Create text stories
This commit is contained in:
parent
973b2264fe
commit
d970d427f8
53 changed files with 2433 additions and 1106 deletions
|
@ -2,18 +2,21 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import Measure from 'react-measure';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import type { LocalizerType, RenderTextCallbackType } from '../types/Util';
|
||||
import type { TextAttachmentType } from '../types/Attachment';
|
||||
import { AddNewLines } from './conversation/AddNewLines';
|
||||
import { Emojify } from './conversation/Emojify';
|
||||
import { StagedLinkPreview } from './conversation/StagedLinkPreview';
|
||||
import { TextAttachmentStyleType } from '../types/Attachment';
|
||||
import { count } from '../util/grapheme';
|
||||
import { getDomain } from '../types/LinkPreview';
|
||||
import { getFontNameByTextScript } from '../util/getFontNameByTextScript';
|
||||
import {
|
||||
COLOR_WHITE_INT,
|
||||
getHexFromNumber,
|
||||
getBackgroundColor,
|
||||
} from '../util/getStoryBackground';
|
||||
|
@ -27,7 +30,6 @@ const renderNewLines: RenderTextCallbackType = ({
|
|||
|
||||
const CHAR_LIMIT_TEXT_LARGE = 50;
|
||||
const CHAR_LIMIT_TEXT_MEDIUM = 200;
|
||||
const COLOR_WHITE_INT = 4294704123;
|
||||
const FONT_SIZE_LARGE = 64;
|
||||
const FONT_SIZE_MEDIUM = 42;
|
||||
const FONT_SIZE_SMALL = 32;
|
||||
|
@ -40,7 +42,9 @@ enum TextSize {
|
|||
|
||||
export type PropsType = {
|
||||
i18n: LocalizerType;
|
||||
isEditingText?: boolean;
|
||||
isThumbnail?: boolean;
|
||||
onChange?: (text: string) => unknown;
|
||||
textAttachment: TextAttachmentType;
|
||||
};
|
||||
|
||||
|
@ -84,9 +88,24 @@ function getFont(
|
|||
return `${fontWeight}${fontSize}pt ${fontName}`;
|
||||
}
|
||||
|
||||
function getTextStyles(
|
||||
textContent: string,
|
||||
textForegroundColor?: number | null,
|
||||
textStyle?: TextAttachmentStyleType | null,
|
||||
i18n?: LocalizerType
|
||||
): { color: string; font: string; textAlign: 'left' | 'center' } {
|
||||
return {
|
||||
color: getHexFromNumber(textForegroundColor || COLOR_WHITE_INT),
|
||||
font: getFont(textContent, getTextSize(textContent), textStyle, i18n),
|
||||
textAlign: getTextSize(textContent) === TextSize.Small ? 'left' : 'center',
|
||||
};
|
||||
}
|
||||
|
||||
export const TextAttachment = ({
|
||||
i18n,
|
||||
isEditingText,
|
||||
isThumbnail,
|
||||
onChange,
|
||||
textAttachment,
|
||||
}: PropsType): JSX.Element | null => {
|
||||
const linkPreview = useRef<HTMLDivElement | null>(null);
|
||||
|
@ -94,6 +113,20 @@ export const TextAttachment = ({
|
|||
number | undefined
|
||||
>();
|
||||
|
||||
const textContent = textAttachment.text || '';
|
||||
|
||||
const textEditorRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const node = textEditorRef.current;
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.focus();
|
||||
node.setSelectionRange(node.value.length, node.value.length);
|
||||
}, [isEditingText]);
|
||||
|
||||
return (
|
||||
<Measure bounds>
|
||||
{({ contentRect, measureRef }) => (
|
||||
|
@ -119,62 +152,72 @@ export const TextAttachment = ({
|
|||
transform: `scale(${(contentRect.bounds?.height || 1) / 1280})`,
|
||||
}}
|
||||
>
|
||||
{textAttachment.text && (
|
||||
{(textContent || onChange) && (
|
||||
<div
|
||||
className="TextAttachment__text"
|
||||
style={{
|
||||
backgroundColor: textAttachment.textBackgroundColor
|
||||
? getHexFromNumber(textAttachment.textBackgroundColor)
|
||||
: 'none',
|
||||
color: getHexFromNumber(
|
||||
textAttachment.textForegroundColor || COLOR_WHITE_INT
|
||||
),
|
||||
font: getFont(
|
||||
textAttachment.text,
|
||||
getTextSize(textAttachment.text),
|
||||
textAttachment.textStyle,
|
||||
i18n
|
||||
),
|
||||
textAlign:
|
||||
getTextSize(textAttachment.text) === TextSize.Small
|
||||
? 'left'
|
||||
: 'center',
|
||||
: 'transparent',
|
||||
}}
|
||||
>
|
||||
<div className="TextAttachment__text__container">
|
||||
<Emojify
|
||||
text={textAttachment.text}
|
||||
renderNonEmoji={renderNewLines}
|
||||
{onChange ? (
|
||||
<TextareaAutosize
|
||||
className="TextAttachment__text__container TextAttachment__text__textarea"
|
||||
disabled={!isEditingText}
|
||||
onChange={ev => onChange(ev.currentTarget.value)}
|
||||
placeholder={i18n('TextAttachment__placeholder')}
|
||||
ref={textEditorRef}
|
||||
style={getTextStyles(
|
||||
textContent,
|
||||
textAttachment.textForegroundColor,
|
||||
textAttachment.textStyle,
|
||||
i18n
|
||||
)}
|
||||
value={textContent}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="TextAttachment__text__container"
|
||||
style={getTextStyles(
|
||||
textContent,
|
||||
textAttachment.textForegroundColor,
|
||||
textAttachment.textStyle,
|
||||
i18n
|
||||
)}
|
||||
>
|
||||
<Emojify
|
||||
text={textContent}
|
||||
renderNonEmoji={renderNewLines}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{textAttachment.preview && (
|
||||
{textAttachment.preview && textAttachment.preview.url && (
|
||||
<>
|
||||
{linkPreviewOffsetTop &&
|
||||
!isThumbnail &&
|
||||
textAttachment.preview.url && (
|
||||
<a
|
||||
className="TextAttachment__preview__tooltip"
|
||||
href={textAttachment.preview.url}
|
||||
rel="noreferrer"
|
||||
style={{
|
||||
top: linkPreviewOffsetTop - 150,
|
||||
}}
|
||||
target="_blank"
|
||||
>
|
||||
<div>
|
||||
<div>{i18n('TextAttachment__preview__link')}</div>
|
||||
<div className="TextAttachment__preview__tooltip__url">
|
||||
{textAttachment.preview.url}
|
||||
</div>
|
||||
{linkPreviewOffsetTop && !isThumbnail && (
|
||||
<a
|
||||
className="TextAttachment__preview__tooltip"
|
||||
href={textAttachment.preview.url}
|
||||
rel="noreferrer"
|
||||
style={{
|
||||
top: linkPreviewOffsetTop - 150,
|
||||
}}
|
||||
target="_blank"
|
||||
>
|
||||
<div>
|
||||
<div>{i18n('TextAttachment__preview__link')}</div>
|
||||
<div className="TextAttachment__preview__tooltip__url">
|
||||
{textAttachment.preview.url}
|
||||
</div>
|
||||
<div className="TextAttachment__preview__tooltip__arrow" />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
<div className="TextAttachment__preview__tooltip__arrow" />
|
||||
</a>
|
||||
)}
|
||||
<div
|
||||
className={classNames('TextAttachment__preview', {
|
||||
'TextAttachment__preview--large': Boolean(
|
||||
className={classNames('TextAttachment__preview-container', {
|
||||
'TextAttachment__preview-container--large': Boolean(
|
||||
textAttachment.preview.title
|
||||
),
|
||||
})}
|
||||
|
@ -186,17 +229,14 @@ export const TextAttachment = ({
|
|||
setLinkPreviewOffsetTop(linkPreview?.current?.offsetTop)
|
||||
}
|
||||
>
|
||||
<div className="TextAttachment__preview__image" />
|
||||
<div className="TextAttachment__preview__title">
|
||||
{textAttachment.preview.title && (
|
||||
<div className="TextAttachment__preview__title__container">
|
||||
{textAttachment.preview.title}
|
||||
</div>
|
||||
)}
|
||||
<div className="TextAttachment__preview__url">
|
||||
{getDomain(String(textAttachment.preview.url))}
|
||||
</div>
|
||||
</div>
|
||||
<StagedLinkPreview
|
||||
domain={getDomain(String(textAttachment.preview.url))}
|
||||
i18n={i18n}
|
||||
image={textAttachment.preview.image}
|
||||
moduleClassName="TextAttachment__preview"
|
||||
title={textAttachment.preview.title || undefined}
|
||||
url={textAttachment.preview.url}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue