mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-17 20:50:29 +03:00
initial commit
This commit is contained in:
commit
026f835a87
176 changed files with 10613 additions and 0 deletions
149
src/app/molecules/message/Message.jsx
Normal file
149
src/app/molecules/message/Message.jsx
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './Message.scss';
|
||||
|
||||
import Linkify from 'linkifyjs/react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import gfm from 'remark-gfm';
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import { coy } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
|
||||
import Text from '../../atoms/text/Text';
|
||||
import RawIcon from '../../atoms/system-icons/RawIcon';
|
||||
import Avatar from '../../atoms/avatar/Avatar';
|
||||
|
||||
import ReplyArrowIC from '../../../../public/res/ic/outlined/reply-arrow.svg';
|
||||
|
||||
const components = {
|
||||
code({
|
||||
// eslint-disable-next-line react/prop-types
|
||||
inline, className, children,
|
||||
}) {
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
return !inline && match ? (
|
||||
<SyntaxHighlighter
|
||||
style={coy}
|
||||
language={match[1]}
|
||||
PreTag="div"
|
||||
showLineNumbers
|
||||
>
|
||||
{String(children).replace(/\n$/, '')}
|
||||
</SyntaxHighlighter>
|
||||
) : (
|
||||
<code className={className}>{String(children)}</code>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
function linkifyContent(content) {
|
||||
return <Linkify options={{ target: { url: '_blank' } }}>{content}</Linkify>;
|
||||
}
|
||||
function genMarkdown(content) {
|
||||
return <ReactMarkdown remarkPlugins={[gfm]} components={components} linkTarget="_blank">{content}</ReactMarkdown>;
|
||||
}
|
||||
|
||||
function PlaceholderMessage() {
|
||||
return (
|
||||
<div className="ph-msg">
|
||||
<div className="ph-msg__avatar-container">
|
||||
<div className="ph-msg__avatar" />
|
||||
</div>
|
||||
<div className="ph-msg__main-container">
|
||||
<div className="ph-msg__header" />
|
||||
<div className="ph-msg__content">
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Message({
|
||||
color, avatarSrc, name, content,
|
||||
time, markdown, contentOnly, reply,
|
||||
edited, reactions,
|
||||
}) {
|
||||
const msgClass = contentOnly ? 'message--content-only' : 'message--full';
|
||||
return (
|
||||
<div className={`message ${msgClass}`}>
|
||||
<div className="message__avatar-container">
|
||||
{!contentOnly && <Avatar imageSrc={avatarSrc} text={name.slice(0, 1)} bgColor={color} size="small" />}
|
||||
</div>
|
||||
<div className="message__main-container">
|
||||
{ !contentOnly && (
|
||||
<div className="message__header">
|
||||
<div style={{ color }} className="message__profile">
|
||||
<Text variant="b1">{name}</Text>
|
||||
</div>
|
||||
<div className="message__time">
|
||||
<Text variant="b3">{time}</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="message__content">
|
||||
{ reply !== null && (
|
||||
<div className="message__reply-content">
|
||||
<Text variant="b2">
|
||||
<RawIcon color={reply.color} size="extra-small" src={ReplyArrowIC} />
|
||||
<span style={{ color: reply.color }}>{reply.to}</span>
|
||||
<>{` ${reply.content}`}</>
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
<div className="text text-b1">
|
||||
{ markdown ? genMarkdown(content) : linkifyContent(content) }
|
||||
</div>
|
||||
{ edited && <Text className="message__edited" variant="b3">(edited)</Text>}
|
||||
{ reactions && (
|
||||
<div className="message__reactions text text-b3 noselect">
|
||||
{
|
||||
reactions.map((reaction) => (
|
||||
<button key={reaction.id} onClick={() => alert('Sending reactions is yet to be implemented.')} type="button" className={`msg__reaction${reaction.active ? ' msg__reaction--active' : ''}`}>
|
||||
{`${reaction.key} ${reaction.count}`}
|
||||
</button>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Message.defaultProps = {
|
||||
color: 'var(--tc-surface-high)',
|
||||
avatarSrc: null,
|
||||
markdown: false,
|
||||
contentOnly: false,
|
||||
reply: null,
|
||||
edited: false,
|
||||
reactions: null,
|
||||
};
|
||||
|
||||
Message.propTypes = {
|
||||
color: PropTypes.string,
|
||||
avatarSrc: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
content: PropTypes.node.isRequired,
|
||||
time: PropTypes.string.isRequired,
|
||||
markdown: PropTypes.bool,
|
||||
contentOnly: PropTypes.bool,
|
||||
reply: PropTypes.shape({
|
||||
color: PropTypes.string.isRequired,
|
||||
to: PropTypes.string.isRequired,
|
||||
content: PropTypes.string.isRequired,
|
||||
}),
|
||||
edited: PropTypes.bool,
|
||||
reactions: PropTypes.arrayOf(PropTypes.exact({
|
||||
id: PropTypes.string,
|
||||
key: PropTypes.string,
|
||||
count: PropTypes.number,
|
||||
active: PropTypes.bool,
|
||||
})),
|
||||
};
|
||||
|
||||
export { Message as default, PlaceholderMessage };
|
||||
293
src/app/molecules/message/Message.scss
Normal file
293
src/app/molecules/message/Message.scss
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
@use '../../atoms/scroll/scrollbar';
|
||||
|
||||
.message,
|
||||
.ph-msg {
|
||||
padding: var(--sp-ultra-tight) var(--sp-normal);
|
||||
padding-right: var(--sp-extra-tight);
|
||||
display: flex;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bg-surface-hover);
|
||||
}
|
||||
|
||||
[dir=rtl] & {
|
||||
padding: {
|
||||
left: var(--sp-extra-tight);
|
||||
right: var(--sp-normal);
|
||||
}
|
||||
}
|
||||
|
||||
&__avatar-container {
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
&__avatar-container,
|
||||
&__profile {
|
||||
margin-right: var(--sp-tight);
|
||||
|
||||
[dir=rtl] & {
|
||||
margin: {
|
||||
left: var(--sp-tight);
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__main-container {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.message {
|
||||
&--full + &--full,
|
||||
&--content-only + &--full,
|
||||
& + .timeline-change,
|
||||
.timeline-change + & {
|
||||
margin-top: var(--sp-normal);
|
||||
}
|
||||
&__avatar-container {
|
||||
width: var(--av-small);
|
||||
}
|
||||
&__reply-content {
|
||||
.text {
|
||||
color: var(--tc-surface-low);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.ic-raw {
|
||||
width: 16px;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
&__edited {
|
||||
color: var(--tc-surface-low);
|
||||
}
|
||||
&__reactions {
|
||||
margin-top: var(--sp-ultra-tight);
|
||||
}
|
||||
}
|
||||
|
||||
.ph-msg {
|
||||
&__avatar {
|
||||
width: var(--av-small);
|
||||
height: var(--av-small);
|
||||
background-color: var(--bg-surface-hover);
|
||||
border-radius: var(--bo-radius);
|
||||
}
|
||||
|
||||
&__header,
|
||||
&__content > div {
|
||||
margin: var(--sp-ultra-tight) 0;
|
||||
margin-right: var(--sp-extra-tight);
|
||||
height: var(--fs-b1);
|
||||
width: 100%;
|
||||
max-width: 100px;
|
||||
background-color: var(--bg-surface-hover);
|
||||
border-radius: calc(var(--bo-radius) / 2);
|
||||
|
||||
[dir=rtl] & {
|
||||
margin: {
|
||||
right: 0;
|
||||
left: var(--sp-extra-tight);
|
||||
}
|
||||
}
|
||||
}
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
&__content > div:nth-child(1n) {
|
||||
max-width: 10%;
|
||||
}
|
||||
&__content > div:nth-child(2n) {
|
||||
max-width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.message__header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
|
||||
& .message__profile {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
color: var(--tc-surface-high);
|
||||
|
||||
& > .text {
|
||||
color: inherit;
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
& .message__time {
|
||||
& > .text {
|
||||
color: var(--tc-surface-low);
|
||||
}
|
||||
}
|
||||
}
|
||||
.message__content {
|
||||
max-width: 640px;
|
||||
word-break: break-word;
|
||||
|
||||
& > .text > * {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
& a {
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
.msg__reaction {
|
||||
--reaction-height: 24px;
|
||||
--reaction-padding: 6px;
|
||||
--reaction-radius: calc(var(--bo-radius) / 2);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--tc-surface-normal);
|
||||
border: 1px solid var(--bg-surface-border);
|
||||
padding: 0 var(--reaction-padding);
|
||||
border-radius: var(--reaction-radius);
|
||||
cursor: pointer;
|
||||
height: var(--reaction-height);
|
||||
|
||||
margin-right: var(--sp-extra-tight);
|
||||
|
||||
[dir=rtl] & {
|
||||
margin: {
|
||||
right: 0;
|
||||
left: var(--sp-extra-tight);
|
||||
}
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
background-color: var(--bg-surface-hover);
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--bg-surface-active)
|
||||
}
|
||||
|
||||
&--active {
|
||||
background-color: var(--bg-caution-active);
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
background-color: var(--bg-caution-hover);
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--bg-caution-active)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// markdown formating
|
||||
.message {
|
||||
& h1,
|
||||
& h2 {
|
||||
color: var(--tc-surface-high);
|
||||
margin: var(--sp-extra-loose) 0 var(--sp-normal);
|
||||
line-height: var(--lh-h1);
|
||||
}
|
||||
& h3,
|
||||
& h4 {
|
||||
color: var(--tc-surface-high);
|
||||
margin: var(--sp-loose) 0 var(--sp-tight);
|
||||
line-height: var(--lh-h2);
|
||||
}
|
||||
& h5,
|
||||
& h6 {
|
||||
color: var(--tc-surface-high);
|
||||
margin: var(--sp-normal) 0 var(--sp-extra-tight);
|
||||
line-height: var(--lh-s1);
|
||||
}
|
||||
& hr {
|
||||
border-color: var(--bg-surface-border);
|
||||
}
|
||||
|
||||
.text img {
|
||||
margin: var(--sp-ultra-tight) 0;
|
||||
max-width: 296px;
|
||||
border-radius: calc(var(--bo-radius) / 2);
|
||||
}
|
||||
|
||||
& p,
|
||||
& pre,
|
||||
& blockquote {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
& pre,
|
||||
& blockquote {
|
||||
margin: var(--sp-ultra-tight) 0;
|
||||
padding: var(--sp-extra-tight);
|
||||
background-color: var(--bg-surface-hover) !important;
|
||||
border-radius: calc(var(--bo-radius) / 2);
|
||||
}
|
||||
& pre {
|
||||
div {
|
||||
background: none !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
span {
|
||||
background: none !important;
|
||||
}
|
||||
.linenumber {
|
||||
min-width: 2.25em !important;
|
||||
}
|
||||
}
|
||||
& code {
|
||||
padding: 0 !important;
|
||||
color: var(--tc-code) !important;
|
||||
white-space: pre-wrap;
|
||||
@include scrollbar.scroll;
|
||||
@include scrollbar.scroll__h;
|
||||
@include scrollbar.scroll--auto-hide;
|
||||
}
|
||||
& pre code {
|
||||
color: var(--tc-surface-normal) !important;
|
||||
}
|
||||
& blockquote {
|
||||
padding-left: var(--sp-extra-tight);
|
||||
border-left: 4px solid var(--bg-surface-active);
|
||||
white-space: initial !important;
|
||||
|
||||
& > * {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
[dir=rtl] & {
|
||||
padding: {
|
||||
left: 0;
|
||||
right: var(--sp-extra-tight);
|
||||
}
|
||||
border: {
|
||||
left: none;
|
||||
right: 4px solid var(--bg-surface-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
& ul,
|
||||
& ol {
|
||||
margin: var(--sp-ultra-tight) 0;
|
||||
padding-left: 24px;
|
||||
white-space: initial !important;
|
||||
|
||||
& > * {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
[dir=rtl] & {
|
||||
padding: {
|
||||
left: 0;
|
||||
right: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
79
src/app/molecules/message/TimelineChange.jsx
Normal file
79
src/app/molecules/message/TimelineChange.jsx
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './TimelineChange.scss';
|
||||
|
||||
// import Linkify from 'linkifyjs/react';
|
||||
|
||||
import Text from '../../atoms/text/Text';
|
||||
import RawIcon from '../../atoms/system-icons/RawIcon';
|
||||
|
||||
import JoinArraowIC from '../../../../public/res/ic/outlined/join-arrow.svg';
|
||||
import LeaveArraowIC from '../../../../public/res/ic/outlined/leave-arrow.svg';
|
||||
import InviteArraowIC from '../../../../public/res/ic/outlined/invite-arrow.svg';
|
||||
import InviteCancelArraowIC from '../../../../public/res/ic/outlined/invite-cancel-arrow.svg';
|
||||
import UserIC from '../../../../public/res/ic/outlined/user.svg';
|
||||
import TickMarkIC from '../../../../public/res/ic/outlined/tick-mark.svg';
|
||||
|
||||
function TimelineChange({ variant, content, time }) {
|
||||
let iconSrc;
|
||||
|
||||
switch (variant) {
|
||||
case 'join':
|
||||
iconSrc = JoinArraowIC;
|
||||
break;
|
||||
case 'leave':
|
||||
iconSrc = LeaveArraowIC;
|
||||
break;
|
||||
case 'invite':
|
||||
iconSrc = InviteArraowIC;
|
||||
break;
|
||||
case 'invite-cancel':
|
||||
iconSrc = InviteCancelArraowIC;
|
||||
break;
|
||||
case 'avatar':
|
||||
iconSrc = UserIC;
|
||||
break;
|
||||
case 'follow':
|
||||
iconSrc = TickMarkIC;
|
||||
break;
|
||||
default:
|
||||
iconSrc = JoinArraowIC;
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="timeline-change">
|
||||
<div className="timeline-change__avatar-container">
|
||||
<RawIcon src={iconSrc} size="extra-small" />
|
||||
</div>
|
||||
<div className="timeline-change__content">
|
||||
<Text variant="b2">
|
||||
{content}
|
||||
{/* <Linkify options={{ target: { url: '_blank' } }}>{content}</Linkify> */}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="timeline-change__time">
|
||||
<Text variant="b3">{time}</Text>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
TimelineChange.defaultProps = {
|
||||
variant: 'other',
|
||||
};
|
||||
|
||||
TimelineChange.propTypes = {
|
||||
variant: PropTypes.oneOf([
|
||||
'join', 'leave', 'invite',
|
||||
'invite-cancel', 'avatar', 'other',
|
||||
'follow',
|
||||
]),
|
||||
content: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.node,
|
||||
]).isRequired,
|
||||
time: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default TimelineChange;
|
||||
39
src/app/molecules/message/TimelineChange.scss
Normal file
39
src/app/molecules/message/TimelineChange.scss
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
.timeline-change {
|
||||
padding: var(--sp-ultra-tight) var(--sp-normal);
|
||||
padding-right: var(--sp-extra-tight);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bg-surface-hover);
|
||||
}
|
||||
|
||||
[dir=rtl] & {
|
||||
padding: {
|
||||
left: var(--sp-extra-tight);
|
||||
right: var(--sp-normal);
|
||||
}
|
||||
}
|
||||
|
||||
&__avatar-container {
|
||||
width: var(--av-small);
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
opacity: 0.38;
|
||||
.ic-raw {
|
||||
background-color: var(--tc-surface-low);
|
||||
}
|
||||
}
|
||||
|
||||
& .text {
|
||||
color: var(--tc-surface-low);
|
||||
}
|
||||
|
||||
&__content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
margin: 0 var(--sp-tight);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue