mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 04:46:40 +07:00
splitting comment text by blocks
This commit is contained in:
parent
d74c9ee739
commit
93afa626db
8 changed files with 206 additions and 158 deletions
20
src/components/comment/CommentTextBlock/index.tsx
Normal file
20
src/components/comment/CommentTextBlock/index.tsx
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import { ICommentBlock } from '~/constants/comment';
|
||||||
|
import styles from './styles.scss';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
block: ICommentBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CommentTextBlock: FC<IProps> = ({ block }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles.text}
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `<p>${block.content}</p>`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { CommentTextBlock };
|
14
src/components/comment/CommentTextBlock/styles.scss
Normal file
14
src/components/comment/CommentTextBlock/styles.scss
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
.text {
|
||||||
|
padding: $gap;
|
||||||
|
font-weight: 300;
|
||||||
|
font: $font_16_medium;
|
||||||
|
line-height: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
color: #cccccc;
|
||||||
|
word-break: break-word;
|
||||||
|
|
||||||
|
b {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { FC, useMemo, memo } from 'react';
|
import React, { FC, useMemo, memo, createElement } from 'react';
|
||||||
import { IComment, IFile } from '~/redux/types';
|
import { IComment, IFile } from '~/redux/types';
|
||||||
import path from 'ramda/es/path';
|
import path from 'ramda/es/path';
|
||||||
import { formatCommentText, getURL, getPrettyDate } from '~/utils/dom';
|
import { formatCommentText, getURL, getPrettyDate } from '~/utils/dom';
|
||||||
|
@ -11,6 +11,7 @@ import reduce from 'ramda/es/reduce';
|
||||||
import { AudioPlayer } from '~/components/media/AudioPlayer';
|
import { AudioPlayer } from '~/components/media/AudioPlayer';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { PRESETS } from '~/constants/urls';
|
import { PRESETS } from '~/constants/urls';
|
||||||
|
import { COMMENT_BLOCK_RENDERERS } from '~/constants/comment';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
comment: IComment;
|
comment: IComment;
|
||||||
|
@ -31,12 +32,11 @@ const CommentContent: FC<IProps> = memo(({ comment }) => {
|
||||||
<>
|
<>
|
||||||
{comment.text && (
|
{comment.text && (
|
||||||
<div className={styles.block}>
|
<div className={styles.block}>
|
||||||
<Group
|
{formatCommentText(path(['user', 'username'], comment), comment.text).map(
|
||||||
className={styles.text}
|
(block, key) =>
|
||||||
dangerouslySetInnerHTML={{
|
COMMENT_BLOCK_RENDERERS[block.type] &&
|
||||||
__html: formatCommentText(path(['user', 'username'], comment), comment.text),
|
createElement(COMMENT_BLOCK_RENDERERS[block.type], { block, key })
|
||||||
}}
|
)}
|
||||||
/>
|
|
||||||
|
|
||||||
<div className={styles.date}>{getPrettyDate(comment.created_at)}</div>
|
<div className={styles.date}>{getPrettyDate(comment.created_at)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
border-top-right-radius: $radius;
|
border-top-right-radius: $radius;
|
||||||
|
@ -36,21 +37,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.text {
|
|
||||||
padding: $gap;
|
|
||||||
font-weight: 300;
|
|
||||||
font: $font_16_medium;
|
|
||||||
line-height: 20px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
position: relative;
|
|
||||||
color: #cccccc;
|
|
||||||
word-break: break-word;
|
|
||||||
|
|
||||||
b {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.date {
|
.date {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
|
|
@ -1,56 +1,51 @@
|
||||||
import React, { FC, useState, useEffect, useCallback } from "react";
|
import React, { FC, useState, useEffect, useCallback } from 'react';
|
||||||
import styles from "./styles.scss";
|
import styles from './styles.scss';
|
||||||
import { connect } from "react-redux";
|
import { connect } from 'react-redux';
|
||||||
import classNames from "classnames";
|
import classNames from 'classnames';
|
||||||
import { selectAuthUser, selectAuthProfile } from "~/redux/auth/selectors";
|
import { selectAuthUser, selectAuthProfile } from '~/redux/auth/selectors';
|
||||||
import { Textarea } from "~/components/input/Textarea";
|
import { Textarea } from '~/components/input/Textarea';
|
||||||
import { Button } from "~/components/input/Button";
|
import { Button } from '~/components/input/Button';
|
||||||
import { Group } from "~/components/containers/Group";
|
import { Group } from '~/components/containers/Group';
|
||||||
import { Filler } from "~/components/containers/Filler";
|
import { Filler } from '~/components/containers/Filler';
|
||||||
import { TextInput } from "~/components/input/TextInput";
|
import { TextInput } from '~/components/input/TextInput';
|
||||||
import { InputText } from "~/components/input/InputText";
|
import { InputText } from '~/components/input/InputText';
|
||||||
import reject from "ramda/es/reject";
|
import reject from 'ramda/es/reject';
|
||||||
import * as AUTH_ACTIONS from "~/redux/auth/actions";
|
import * as AUTH_ACTIONS from '~/redux/auth/actions';
|
||||||
import { ERROR_LITERAL } from "~/constants/errors";
|
import { ERROR_LITERAL } from '~/constants/errors';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
user: selectAuthUser(state),
|
user: selectAuthUser(state),
|
||||||
profile: selectAuthProfile(state)
|
profile: selectAuthProfile(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
authPatchUser: AUTH_ACTIONS.authPatchUser,
|
authPatchUser: AUTH_ACTIONS.authPatchUser,
|
||||||
authSetProfile: AUTH_ACTIONS.authSetProfile
|
authSetProfile: AUTH_ACTIONS.authSetProfile,
|
||||||
};
|
};
|
||||||
|
|
||||||
type IProps = ReturnType<typeof mapStateToProps> &
|
type IProps = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
|
||||||
typeof mapDispatchToProps & {};
|
|
||||||
|
|
||||||
const ProfileSettingsUnconnected: FC<IProps> = ({
|
const ProfileSettingsUnconnected: FC<IProps> = ({
|
||||||
user,
|
user,
|
||||||
profile: { patch_errors },
|
profile: { patch_errors },
|
||||||
authPatchUser,
|
authPatchUser,
|
||||||
authSetProfile
|
authSetProfile,
|
||||||
}) => {
|
}) => {
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState('');
|
||||||
const [new_password, setNewPassword] = useState("");
|
const [new_password, setNewPassword] = useState('');
|
||||||
|
|
||||||
const [data, setData] = useState(user);
|
const [data, setData] = useState(user);
|
||||||
|
|
||||||
const setDescription = useCallback(
|
const setDescription = useCallback(description => setData({ ...data, description }), [
|
||||||
description => setData({ ...data, description }),
|
|
||||||
[data, setData]
|
|
||||||
);
|
|
||||||
|
|
||||||
const setEmail = useCallback(email => setData({ ...data, email }), [
|
|
||||||
data,
|
data,
|
||||||
setData
|
setData,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const setUsername = useCallback(username => setData({ ...data, username }), [
|
const setEmail = useCallback(email => setData({ ...data, email }), [data, setData]);
|
||||||
data,
|
|
||||||
setData
|
const setUsername = useCallback(username => setData({ ...data, username }), [data, setData]);
|
||||||
]);
|
|
||||||
|
const setFullname = useCallback(fullname => setData({ ...data, fullname }), [data, setData]);
|
||||||
|
|
||||||
const onSubmit = useCallback(
|
const onSubmit = useCallback(
|
||||||
event => {
|
event => {
|
||||||
|
@ -58,10 +53,11 @@ const ProfileSettingsUnconnected: FC<IProps> = ({
|
||||||
|
|
||||||
const fields = reject(el => !el)({
|
const fields = reject(el => !el)({
|
||||||
email: data.email !== user.email && data.email,
|
email: data.email !== user.email && data.email,
|
||||||
|
fullname: data.fullname !== user.fullname && data.fullname,
|
||||||
username: data.username !== user.username && data.username,
|
username: data.username !== user.username && data.username,
|
||||||
password: password.length > 0 && password,
|
password: password.length > 0 && password,
|
||||||
new_password: new_password.length > 0 && new_password,
|
new_password: new_password.length > 0 && new_password,
|
||||||
description: data.description !== user.description && data.description
|
description: data.description !== user.description && data.description,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Object.values(fields).length === 0) return;
|
if (Object.values(fields).length === 0) return;
|
||||||
|
@ -78,15 +74,18 @@ const ProfileSettingsUnconnected: FC<IProps> = ({
|
||||||
return (
|
return (
|
||||||
<form className={styles.wrap} onSubmit={onSubmit}>
|
<form className={styles.wrap} onSubmit={onSubmit}>
|
||||||
<Group>
|
<Group>
|
||||||
<Textarea
|
<InputText
|
||||||
value={data.description}
|
value={data.fullname}
|
||||||
handler={setDescription}
|
handler={setFullname}
|
||||||
title="Описание"
|
title="Полное имя"
|
||||||
|
error={patch_errors.fullname && ERROR_LITERAL[patch_errors.fullname]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Textarea value={data.description} handler={setDescription} title="Описание" />
|
||||||
|
|
||||||
<div className={styles.small}>
|
<div className={styles.small}>
|
||||||
Описание будет видно на странице профиля. Здесь работают те же правила
|
Описание будет видно на странице профиля. Здесь работают те же правила оформления, что и в
|
||||||
оформления, что и в комментариях.
|
комментариях.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Group className={styles.pad}>
|
<Group className={styles.pad}>
|
||||||
|
@ -94,9 +93,7 @@ const ProfileSettingsUnconnected: FC<IProps> = ({
|
||||||
value={data.username}
|
value={data.username}
|
||||||
handler={setUsername}
|
handler={setUsername}
|
||||||
title="Логин"
|
title="Логин"
|
||||||
error={
|
error={patch_errors.username && ERROR_LITERAL[patch_errors.username]}
|
||||||
patch_errors.username && ERROR_LITERAL[patch_errors.username]
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<InputText
|
<InputText
|
||||||
|
@ -111,10 +108,7 @@ const ProfileSettingsUnconnected: FC<IProps> = ({
|
||||||
handler={setNewPassword}
|
handler={setNewPassword}
|
||||||
title="Новый пароль"
|
title="Новый пароль"
|
||||||
type="password"
|
type="password"
|
||||||
error={
|
error={patch_errors.new_password && ERROR_LITERAL[patch_errors.new_password]}
|
||||||
patch_errors.new_password &&
|
|
||||||
ERROR_LITERAL[patch_errors.new_password]
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div />
|
<div />
|
||||||
|
@ -124,9 +118,7 @@ const ProfileSettingsUnconnected: FC<IProps> = ({
|
||||||
handler={setPassword}
|
handler={setPassword}
|
||||||
title="Старый пароль"
|
title="Старый пароль"
|
||||||
type="password"
|
type="password"
|
||||||
error={
|
error={patch_errors.password && ERROR_LITERAL[patch_errors.password]}
|
||||||
patch_errors.password && ERROR_LITERAL[patch_errors.password]
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={styles.small}>
|
<div className={styles.small}>
|
||||||
|
|
33
src/constants/comment.ts
Normal file
33
src/constants/comment.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { CommentTextBlock } from '~/components/comment/CommentTextBlock';
|
||||||
|
|
||||||
|
export const COMMENT_BLOCK_TYPES = {
|
||||||
|
TEXT: 'TEXT',
|
||||||
|
MARK: 'MARK',
|
||||||
|
EMBED: 'EMBED',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const COMMENT_BLOCK_DETECTORS = [
|
||||||
|
{
|
||||||
|
type: COMMENT_BLOCK_TYPES.EMBED,
|
||||||
|
test: /(https?:\/\/(www\.)?(youtube\.com|youtu\.be)\/(watch)?(\?v=)?[\w\-]+)/gim,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: COMMENT_BLOCK_TYPES.MARK,
|
||||||
|
test: /^[\n\s]{0,}?<\.{3}>[\n\s]{0,}$/gi,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: COMMENT_BLOCK_TYPES.TEXT,
|
||||||
|
test: /^.*$/gi,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export type ICommentBlock = {
|
||||||
|
type: typeof COMMENT_BLOCK_TYPES[keyof typeof COMMENT_BLOCK_TYPES];
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const COMMENT_BLOCK_RENDERERS = {
|
||||||
|
[COMMENT_BLOCK_TYPES.TEXT]: CommentTextBlock,
|
||||||
|
[COMMENT_BLOCK_TYPES.MARK]: CommentTextBlock,
|
||||||
|
[COMMENT_BLOCK_TYPES.EMBED]: CommentTextBlock,
|
||||||
|
};
|
|
@ -21,51 +21,55 @@ render(
|
||||||
/*
|
/*
|
||||||
|
|
||||||
[Stage 0]:
|
[Stage 0]:
|
||||||
|
- <...> format
|
||||||
|
- youtube embeds
|
||||||
|
|
||||||
- check if email is registered at social login
|
- check if email is registered at social login
|
||||||
- friendship
|
- friendship
|
||||||
- signup?
|
|
||||||
- cover change
|
- cover change
|
||||||
- sticky header
|
|
||||||
- mobile header
|
|
||||||
- profile cover upload
|
- profile cover upload
|
||||||
|
- user access time update
|
||||||
|
|
||||||
- illustrate restoreRequestDialog
|
- illustrate restoreRequestDialog
|
||||||
- illustrate 404
|
- illustrate 404
|
||||||
- illustrate login
|
- illustrate login
|
||||||
|
|
||||||
[stage 1]
|
[stage 1]
|
||||||
- import videos
|
- signup?
|
||||||
- import graffiti
|
- import videos
|
||||||
- text post can also has songs http://vault48.org/post5052
|
- import graffiti
|
||||||
- fulltext search: https://github.com/typeorm/typeorm/issues/3191
|
- text post can also has songs http://vault48.org/post5052
|
||||||
- zoom: https://manuelstofer.github.io/pinchzoom/
|
- fulltext search: https://github.com/typeorm/typeorm/issues/3191
|
||||||
|
- zoom: https://manuelstofer.github.io/pinchzoom/
|
||||||
|
|
||||||
- notifications (node, comment)
|
- notifications (node, comment)
|
||||||
|
|
||||||
- social integration (assimilate)
|
- social integration (assimilate)
|
||||||
- comment editing
|
- comment editing
|
||||||
|
|
||||||
Done:
|
Done:
|
||||||
- password restore
|
- mobile header
|
||||||
- avatar upload
|
- sticky header
|
||||||
- flow updates
|
- password restore
|
||||||
- flow infinite scroll
|
- avatar upload
|
||||||
- better node brief update
|
- flow updates
|
||||||
- fix: text nodes cell has no preview (actually, that's a problem of brief)
|
- flow infinite scroll
|
||||||
- relocate files
|
- better node brief update
|
||||||
- backend: exclude node covers on import
|
- fix: text nodes cell has no preview (actually, that's a problem of brief)
|
||||||
- profile editing
|
- relocate files
|
||||||
- notifications (messages)
|
- backend: exclude node covers on import
|
||||||
- profile modal
|
- profile editing
|
||||||
- messages
|
- notifications (messages)
|
||||||
- better dialogs: https://codepen.io/muemue/pen/abbEMMy
|
- profile modal
|
||||||
- imagecaching at backend
|
- messages
|
||||||
- social integration (login, signup)
|
- better dialogs: https://codepen.io/muemue/pen/abbEMMy
|
||||||
- boris with comments (import)
|
- imagecaching at backend
|
||||||
- boris with comments (layout)
|
- social integration (login, signup)
|
||||||
- fix: user receives his own notifications :-(
|
- boris with comments (import)
|
||||||
- fix: node related and albums should exclude node itself
|
- boris with comments (layout)
|
||||||
- fix: select node and edit it. All images will be not loaded
|
- fix: user receives his own notifications :-(
|
||||||
- fix: text nodes cell not clickable
|
- fix: node related and albums should exclude node itself
|
||||||
- fix: text nodes should not have 'no comments yet badge
|
- fix: select node and edit it. All images will be not loaded
|
||||||
|
- fix: text nodes cell not clickable
|
||||||
|
- fix: text nodes should not have 'no comments yet badge
|
||||||
*/
|
*/
|
||||||
|
|
101
src/utils/dom.ts
101
src/utils/dom.ts
|
@ -1,23 +1,20 @@
|
||||||
import { IFile } from "~/redux/types";
|
import { IFile, ValueOf } from '~/redux/types';
|
||||||
import formatDistanceToNow from "date-fns/formatDistanceToNow";
|
import formatDistanceToNow from 'date-fns/formatDistanceToNow';
|
||||||
import { ru } from "date-fns/locale";
|
import { ru } from 'date-fns/locale';
|
||||||
import Axios from "axios";
|
import Axios from 'axios';
|
||||||
import { PRESETS } from "~/constants/urls";
|
import { PRESETS } from '~/constants/urls';
|
||||||
|
import { ICommentBlock, COMMENT_BLOCK_DETECTORS, COMMENT_BLOCK_TYPES } from '~/constants/comment';
|
||||||
|
|
||||||
export const getStyle = (oElm: any, strCssRule: string) => {
|
export const getStyle = (oElm: any, strCssRule: string) => {
|
||||||
if (document.defaultView && document.defaultView.getComputedStyle) {
|
if (document.defaultView && document.defaultView.getComputedStyle) {
|
||||||
return document.defaultView
|
return document.defaultView.getComputedStyle(oElm, '').getPropertyValue(strCssRule);
|
||||||
.getComputedStyle(oElm, "")
|
|
||||||
.getPropertyValue(strCssRule);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oElm.currentStyle) {
|
if (oElm.currentStyle) {
|
||||||
return oElm.currentStyle[
|
return oElm.currentStyle[strCssRule.replace(/-(\w)/g, (strMatch, p1) => p1.toUpperCase())];
|
||||||
strCssRule.replace(/-(\w)/g, (strMatch, p1) => p1.toUpperCase())
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
|
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
|
||||||
|
@ -25,7 +22,7 @@ function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
x: centerX + radius * Math.cos(angleInRadians),
|
x: centerX + radius * Math.cos(angleInRadians),
|
||||||
y: centerY + radius * Math.sin(angleInRadians)
|
y: centerY + radius * Math.sin(angleInRadians),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,10 +39,10 @@ export const describeArc = (
|
||||||
const largeArcFlag = endAngle - startAngle <= 180 ? 0 : 1;
|
const largeArcFlag = endAngle - startAngle <= 180 ? 0 : 1;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"M",
|
'M',
|
||||||
start.x,
|
start.x,
|
||||||
start.y,
|
start.y,
|
||||||
"A",
|
'A',
|
||||||
radius,
|
radius,
|
||||||
radius,
|
radius,
|
||||||
0,
|
0,
|
||||||
|
@ -53,62 +50,66 @@ export const describeArc = (
|
||||||
0,
|
0,
|
||||||
end.x,
|
end.x,
|
||||||
end.y,
|
end.y,
|
||||||
"L",
|
'L',
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
"L",
|
'L',
|
||||||
start.x,
|
start.x,
|
||||||
start.y
|
start.y,
|
||||||
].join(" ");
|
].join(' ');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getURL = (
|
export const getURL = (file: Partial<IFile>, size?: typeof PRESETS[keyof typeof PRESETS]) => {
|
||||||
file: Partial<IFile>,
|
|
||||||
size?: typeof PRESETS[keyof typeof PRESETS]
|
|
||||||
) => {
|
|
||||||
if (!file || !file.url) return null;
|
if (!file || !file.url) return null;
|
||||||
|
|
||||||
if (size) {
|
if (size) {
|
||||||
return file.url
|
return file.url
|
||||||
.replace(
|
.replace('REMOTE_CURRENT://', `${process.env.REMOTE_CURRENT}cache/${size}/`)
|
||||||
"REMOTE_CURRENT://",
|
.replace('REMOTE_OLD://', process.env.REMOTE_OLD);
|
||||||
`${process.env.REMOTE_CURRENT}cache/${size}/`
|
|
||||||
)
|
|
||||||
.replace("REMOTE_OLD://", process.env.REMOTE_OLD);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return file.url
|
return file.url
|
||||||
.replace("REMOTE_CURRENT://", process.env.REMOTE_CURRENT)
|
.replace('REMOTE_CURRENT://', process.env.REMOTE_CURRENT)
|
||||||
.replace("REMOTE_OLD://", process.env.REMOTE_OLD);
|
.replace('REMOTE_OLD://', process.env.REMOTE_OLD);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatText = (text: string): string =>
|
export const formatText = (text: string): string =>
|
||||||
!text
|
!text
|
||||||
? ""
|
? ''
|
||||||
: text
|
: text
|
||||||
.replace(/\n{1,}/gim, "\n")
|
.replace(
|
||||||
.replace(/</g, "<")
|
/(https?:\/\/(www\.)?(youtube\.com|youtu\.be)\/(watch)?(\?v=)?[\w\-]+)/gim,
|
||||||
.replace(/>/g, ">")
|
'\n$1\n'
|
||||||
|
)
|
||||||
|
.replace(/\n{1,}/gim, '\n')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
.replace(
|
.replace(
|
||||||
/~([\wа-яА-Я\-]+)/giu,
|
/~([\wа-яА-Я\-]+)/giu,
|
||||||
"<span class=\"username\" onClick=\"window.postMessage({ type: 'username', username: '$1'});\">~$1</span>"
|
'<span class="username" onClick="window.postMessage({ type: \'username\', username: \'$1\'});">~$1</span>'
|
||||||
)
|
)
|
||||||
.replace(/:\/\//gim, ":|--|")
|
.replace(/:\/\//gim, ':|--|')
|
||||||
.replace(/(\/\/[^\n]+)/gim, '<span class="grey">$1</span>')
|
.replace(/(\/\/[^\n]+)/gim, '<span class="grey">$1</span>')
|
||||||
.replace(/(\/\*[\s\S]*?\*\/)/gim, '<span class="grey">$1</span>')
|
.replace(/(\/\*[\s\S]*?\*\/)/gim, '<span class="grey">$1</span>')
|
||||||
.replace(/:\|--\|/gim, "://")
|
.replace(/:\|--\|/gim, '://')
|
||||||
.split("\n")
|
.split('\n')
|
||||||
.filter(el => el.trim().length)
|
.filter(el => el.trim().length)
|
||||||
.map(el => `<p>${el}</p>`)
|
// .map(el => `<p>${el}</p>`)
|
||||||
.join("");
|
.join('\n');
|
||||||
|
|
||||||
export const formatCommentText = (author: string, text: string): string =>
|
export const findBlockType = (line: string): ValueOf<typeof COMMENT_BLOCK_TYPES> => {
|
||||||
text
|
const match = Object.values(COMMENT_BLOCK_DETECTORS).find(detector => line.match(detector.test));
|
||||||
? formatText(text).replace(
|
return (match && match.type) || COMMENT_BLOCK_TYPES.TEXT;
|
||||||
/^<p>/,
|
};
|
||||||
author ? `<p><b class="comment-author">${author}: </b>` : "<p>"
|
|
||||||
)
|
export const splitCommentByBlocks = (text: string): ICommentBlock[] =>
|
||||||
: "";
|
text.split('\n').map(line => ({
|
||||||
|
type: findBlockType(line),
|
||||||
|
content: line,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const formatCommentText = (author: string, text: string): ICommentBlock[] =>
|
||||||
|
text ? splitCommentByBlocks(formatText(text)) : null;
|
||||||
|
|
||||||
export const formatCellText = (text: string): string => formatText(text);
|
export const formatCellText = (text: string): string => formatText(text);
|
||||||
|
|
||||||
|
@ -116,13 +117,11 @@ export const getPrettyDate = (date: string): string =>
|
||||||
formatDistanceToNow(new Date(date), {
|
formatDistanceToNow(new Date(date), {
|
||||||
locale: ru,
|
locale: ru,
|
||||||
includeSeconds: true,
|
includeSeconds: true,
|
||||||
addSuffix: true
|
addSuffix: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getYoutubeTitle = async (id: string) => {
|
export const getYoutubeTitle = async (id: string) => {
|
||||||
Axios.get(`http://youtube.com/get_video_info?video_id=${id}`).then(
|
Axios.get(`http://youtube.com/get_video_info?video_id=${id}`).then(console.log);
|
||||||
console.log
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
(<any>window).getYoutubeTitle = getYoutubeTitle;
|
(<any>window).getYoutubeTitle = getYoutubeTitle;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue