refactor: new send input
This commit is contained in:
@@ -161,7 +161,7 @@ export default function ChannelModal({ personal = false, closeModal }) {
|
||||
<Button
|
||||
disabled={isLoading}
|
||||
onClick={handleCreate}
|
||||
className="normal main"
|
||||
className="normal"
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import * as React from "react";
|
||||
import ReactMde from "react-mde";
|
||||
import * as Showdown from "showdown";
|
||||
import "react-mde/lib/styles/css/react-mde-all.css";
|
||||
|
||||
const converter = new Showdown.Converter({
|
||||
tables: true,
|
||||
simplifiedAutoLink: true,
|
||||
strikethrough: true,
|
||||
tasklists: true,
|
||||
});
|
||||
|
||||
export default function MarkdownEditer() {
|
||||
const [value, setValue] = React.useState("**Hello world!!!**");
|
||||
const [selectedTab, setSelectedTab] = React.useState("write");
|
||||
return (
|
||||
<div className="container">
|
||||
<ReactMde
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
selectedTab={selectedTab}
|
||||
onTabChange={setSelectedTab}
|
||||
generateMarkdownPreview={(markdown) =>
|
||||
Promise.resolve(converter.makeHtml(markdown))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -65,11 +65,23 @@ export default function Reply({ mid }) {
|
||||
const { data, users } = useSelector((store) => {
|
||||
return { data: store.message[mid], users: store.contacts.byId };
|
||||
});
|
||||
const handleClick = (evt) => {
|
||||
const { mid } = evt.currentTarget.dataset;
|
||||
const msgEle = document.querySelector(`[data-msg-mid='${mid}']`);
|
||||
if (msgEle) {
|
||||
msgEle.dataset.highlight = true;
|
||||
msgEle.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
setTimeout(() => {
|
||||
console.log("scroll view", msgEle);
|
||||
msgEle.dataset.highlight = false;
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
if (!data) return null;
|
||||
const currUser = users[data.from_uid];
|
||||
if (!currUser) return null;
|
||||
return (
|
||||
<Styled data-mid={mid} className="reply">
|
||||
<Styled data-mid={mid} className="reply" onClick={handleClick}>
|
||||
<div className="user">
|
||||
<Avatar className="avatar" url={currUser.avatar} name={currUser.name} />
|
||||
<span className="name">{currUser.name}</span>
|
||||
|
||||
@@ -70,7 +70,7 @@ function Message({ contextId = 0, mid = "", context = "user" }) {
|
||||
// if (!message) return null;
|
||||
return (
|
||||
<StyledWrapper
|
||||
data-mid={mid}
|
||||
data-msg-mid={mid}
|
||||
ref={myRef}
|
||||
className={`message ${inView ? "in_view" : ""}`}
|
||||
>
|
||||
|
||||
@@ -12,6 +12,9 @@ const StyledMsg = styled.div`
|
||||
&.in_view {
|
||||
content-visibility: visible;
|
||||
}
|
||||
&[data-highlight="true"] {
|
||||
background: #f5f6f7;
|
||||
}
|
||||
&:hover,
|
||||
&.preview {
|
||||
background: #f5f6f7;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// import React from 'react'
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import styled from "styled-components";
|
||||
const Styled = styled.div`
|
||||
font-family: "Helvetica Neue", sans;
|
||||
@@ -148,7 +147,7 @@ const Styled = styled.div`
|
||||
export default function MrakdownRender({ content }) {
|
||||
return (
|
||||
<Styled>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
|
||||
<ReactMarkdown>{content}</ReactMarkdown>
|
||||
</Styled>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,25 +1,33 @@
|
||||
import { useState } from "react";
|
||||
// import { useState } from "react";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import styled from "styled-components";
|
||||
const StyledBtn = styled.button`
|
||||
background: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
svg {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
`;
|
||||
import Picker from "../EmojiPicker";
|
||||
import SmileIcon from "../../../assets/icons/emoji.smile.svg";
|
||||
|
||||
export default function EmojiPicker({ selectEmoji }) {
|
||||
const [emojiPickerVisible, setEmojiPickerVisible] = useState(false);
|
||||
const toggleEmojiPicker = () => {
|
||||
setEmojiPickerVisible((prev) => !prev);
|
||||
};
|
||||
const handleSelect = (emoji) => {
|
||||
selectEmoji(emoji.native);
|
||||
setEmojiPickerVisible(false);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<button className="toggle" onClick={toggleEmojiPicker}>
|
||||
😄
|
||||
</button>
|
||||
{emojiPickerVisible && (
|
||||
<div className="picker">
|
||||
<Picker onSelect={handleSelect} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
<Tippy
|
||||
offset={[-18, 25]}
|
||||
interactive
|
||||
placement="top-start"
|
||||
trigger="click"
|
||||
content={<Picker onSelect={handleSelect} />}
|
||||
>
|
||||
<StyledBtn>
|
||||
<SmileIcon />
|
||||
</StyledBtn>
|
||||
</Tippy>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { removeReplyingMessage } from "../../../app/slices/message";
|
||||
import styled from "styled-components";
|
||||
const Styled = styled.div`
|
||||
z-index: 999;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,74 +1,37 @@
|
||||
// import React from 'react'
|
||||
import { RiMarkdownLine, RiMarkdownFill } from "react-icons/ri";
|
||||
import styled from "styled-components";
|
||||
import Button from "../styled/Button";
|
||||
import EmojiPicker from "./EmojiPicker";
|
||||
import addIcon from "../../../assets/icons/add.svg?url";
|
||||
|
||||
import AddIcon from "../../../assets/icons/add.solid.svg";
|
||||
import MarkdownIcon from "../../../assets/icons/markdown.svg";
|
||||
const Styled = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 5px;
|
||||
.left {
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
.md {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
.md {
|
||||
cursor: pointer;
|
||||
}
|
||||
.add {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
input {
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.emoji {
|
||||
position: relative;
|
||||
.toggle {
|
||||
font-size: 22px;
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
.picker {
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
right: -15px;
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
.right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
.send {
|
||||
/* font-size: 16px;
|
||||
font-weight: bold;
|
||||
background: none; */
|
||||
.add {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
input {
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.divider {
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
background-color: #ccc;
|
||||
margin: 0 4px;
|
||||
}
|
||||
`;
|
||||
export default function Toolbar({
|
||||
contentType = "text",
|
||||
updateContentType,
|
||||
handleUpload,
|
||||
selectEmoji,
|
||||
handleSend,
|
||||
}) {
|
||||
const toggleMarkdown = () => {
|
||||
@@ -76,35 +39,18 @@ export default function Toolbar({
|
||||
};
|
||||
return (
|
||||
<Styled>
|
||||
<div className="left">
|
||||
<div className="add">
|
||||
<img src={addIcon} />
|
||||
<input
|
||||
multiple={true}
|
||||
onChange={handleUpload}
|
||||
type="file"
|
||||
name="file"
|
||||
id="file"
|
||||
/>
|
||||
</div>
|
||||
<div className="divider"></div>
|
||||
<div className="emoji">
|
||||
<EmojiPicker selectEmoji={selectEmoji} />
|
||||
</div>
|
||||
<div className="md" onClick={toggleMarkdown}>
|
||||
{contentType == "markdown" ? (
|
||||
<RiMarkdownFill size={24} />
|
||||
) : (
|
||||
<RiMarkdownLine size={24} />
|
||||
)}
|
||||
</div>
|
||||
<div className="md" onClick={toggleMarkdown}>
|
||||
{contentType == "markdown" ? <MarkdownIcon /> : <MarkdownIcon />}
|
||||
</div>
|
||||
<div className="right">
|
||||
{contentType == "markdown" && (
|
||||
<Button className="send main" onClick={handleSend}>
|
||||
Send
|
||||
</Button>
|
||||
)}
|
||||
<div className="add">
|
||||
<AddIcon />
|
||||
<input
|
||||
multiple={true}
|
||||
onChange={handleUpload}
|
||||
type="file"
|
||||
name="file"
|
||||
id="file"
|
||||
/>
|
||||
</div>
|
||||
</Styled>
|
||||
);
|
||||
|
||||
@@ -71,7 +71,7 @@ export default function UploadModal({
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
className="upload main"
|
||||
className="upload"
|
||||
disabled={channelSending || userSending}
|
||||
onClick={handleUpload}
|
||||
>
|
||||
|
||||
@@ -12,7 +12,8 @@ import useFiles from "./useFiles";
|
||||
import UploadModal from "./UploadModal";
|
||||
import Replying from "./Replying";
|
||||
import Toolbar from "./Toolbar";
|
||||
import MarkdownEditor from "../MarkdownEditer";
|
||||
import EmojiPicker from "./EmojiPicker";
|
||||
import RichInput from "../RichInput";
|
||||
const Types = {
|
||||
channel: "#",
|
||||
user: "@",
|
||||
@@ -27,9 +28,9 @@ export default function Send({
|
||||
const [contentType, setContentType] = useState("text");
|
||||
const [replyMessage] = useReplyMessageMutation();
|
||||
const { files, setFiles, resetFiles } = useFiles([]);
|
||||
const inputRef = useRef();
|
||||
const [shift, setShift] = useState(false);
|
||||
const [enter, setEnter] = useState(false);
|
||||
// const [shift, setShift] = useState(false);
|
||||
// const [enter, setEnter] = useState(false);
|
||||
const [editor, setEditor] = useState(null);
|
||||
const [markdown, setMarkdown] = useState("");
|
||||
const [msg, setMsg] = useState("");
|
||||
const dispatch = useDispatch();
|
||||
@@ -53,44 +54,26 @@ export default function Send({
|
||||
] = useSendChannelMsgMutation();
|
||||
const sendMessage = type == "channel" ? sendChannelMsg : sendMsg;
|
||||
const sendingMessage = userSending || channelSending;
|
||||
useKey(
|
||||
"Shift",
|
||||
(e) => {
|
||||
console.log("shift", e.type);
|
||||
setShift(e.type == "keydown");
|
||||
},
|
||||
{ eventTypes: ["keydown", "keyup"], target: inputRef }
|
||||
);
|
||||
const handleMsgChange = (evt) => {
|
||||
if (enter && !shift) {
|
||||
handleSendMessage();
|
||||
} else {
|
||||
setMsg(evt.target.value);
|
||||
}
|
||||
const insertEmoji = (emoji) => {
|
||||
console.log("insert emoji", emoji, editor);
|
||||
editor.insertText(emoji);
|
||||
};
|
||||
const handleInputKeydown = (e) => {
|
||||
console.log("keydown event", e);
|
||||
setEnter(e.key === "Enter");
|
||||
};
|
||||
const selectEmoji = (emoji) => {
|
||||
setMsg((prev) => `${prev}${emoji}`);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (inputRef) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, [msg, replying_mid]);
|
||||
const handleUpload = (evt) => {
|
||||
setFiles([...evt.target.files]);
|
||||
};
|
||||
const handleSendMessage = () => {
|
||||
if (!msg || !id || sendingMessage) return;
|
||||
console.log("send message", msg);
|
||||
const content_type = msg ? "text" : "markdown";
|
||||
const content = msg ? msg : markdown;
|
||||
console.log("current message", markdown, msg);
|
||||
if (replying_mid) {
|
||||
console.log("replying", replying_mid);
|
||||
replyMessage({
|
||||
id,
|
||||
reply_mid: replying_mid,
|
||||
content: msg,
|
||||
type: content_type,
|
||||
content,
|
||||
context: type,
|
||||
from_uid,
|
||||
});
|
||||
@@ -98,58 +81,30 @@ export default function Send({
|
||||
} else {
|
||||
sendMessage({
|
||||
id,
|
||||
content: msg,
|
||||
type: content_type,
|
||||
content,
|
||||
from_uid,
|
||||
properties: { local_id: new Date().getTime() },
|
||||
});
|
||||
}
|
||||
setMsg("");
|
||||
};
|
||||
const sendMarkdown = () => {
|
||||
console.log("markdown", markdown, markdown.endsWith("\\"));
|
||||
sendMessage({
|
||||
id,
|
||||
content: markdown,
|
||||
from_uid,
|
||||
properties: { local_id: new Date().getTime() },
|
||||
type: "markdown",
|
||||
});
|
||||
setMarkdown("");
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledSend className={`send ${replying_mid ? "reply" : ""} ${type}`}>
|
||||
{replying_mid && <Replying mid={replying_mid} id={id} />}
|
||||
|
||||
<div className="input">
|
||||
{contentType == "markdown" ? (
|
||||
<MarkdownEditor value={markdown} updateValue={setMarkdown} />
|
||||
) : (
|
||||
<TextareaAutosize
|
||||
autoFocus
|
||||
onFocus={(e) =>
|
||||
e.currentTarget.setSelectionRange(
|
||||
e.currentTarget.value.length,
|
||||
e.currentTarget.value.length
|
||||
)
|
||||
}
|
||||
ref={inputRef}
|
||||
className="content"
|
||||
maxRows={8}
|
||||
minRows={1}
|
||||
onKeyDown={handleInputKeydown}
|
||||
onChange={handleMsgChange}
|
||||
value={msg}
|
||||
placeholder={`Send to ${Types[type]}${name}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<EmojiPicker selectEmoji={insertEmoji} />
|
||||
<RichInput
|
||||
setEditor={setEditor}
|
||||
placeholder={`Send to ${Types[type]}${name} `}
|
||||
sendMessage={handleSendMessage}
|
||||
updateMarkdown={setMarkdown}
|
||||
updatePureText={setMsg}
|
||||
/>
|
||||
<Toolbar
|
||||
handleSend={sendMarkdown}
|
||||
contentType={contentType}
|
||||
updateContentType={setContentType}
|
||||
selectEmoji={selectEmoji}
|
||||
handleUpload={handleUpload}
|
||||
/>
|
||||
</StyledSend>
|
||||
|
||||
@@ -1,56 +1,24 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledSend = styled.div`
|
||||
position: absolute;
|
||||
bottom: -70px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
position: relative;
|
||||
background: #e5e7eb;
|
||||
border-radius: 8px;
|
||||
width: calc(100% - 32px);
|
||||
min-height: 54px;
|
||||
border-radius: var(--br);
|
||||
width: 100%;
|
||||
/* min-height: 54px; */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 5px;
|
||||
padding: 4px 18px;
|
||||
justify-content: space-between;
|
||||
gap: 15px;
|
||||
padding: 14px 18px;
|
||||
/* margin: 0 16px; */
|
||||
&.user {
|
||||
bottom: 10px;
|
||||
}
|
||||
&.reply {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
.input {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
.content {
|
||||
resize: unset;
|
||||
outline: none;
|
||||
padding: 4px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #616161;
|
||||
width: 100%;
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 2px 6px;
|
||||
background: green;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
/* padding: 4px 0; */
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ export default function ProfileBasicEditModal({
|
||||
buttons={
|
||||
<>
|
||||
<Button onClick={closeModal}>Cancel</Button>
|
||||
<Button onClick={handleUpdate} className="main">
|
||||
<Button onClick={handleUpdate}>
|
||||
{isLoading ? "Updating" : `Done`}
|
||||
</Button>
|
||||
</>
|
||||
|
||||
@@ -63,7 +63,7 @@ export default function ConfigFirebase() {
|
||||
<div className="input">
|
||||
<Label htmlFor="desc">Private Key</Label>
|
||||
<Textarea
|
||||
rows={15}
|
||||
rows={10}
|
||||
disabled={!enabled}
|
||||
data-type="private_key"
|
||||
onChange={handleChange}
|
||||
|
||||
@@ -61,6 +61,9 @@ const StyledWrapper = styled.div`
|
||||
> .right {
|
||||
background-color: #fff;
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
/* max-height: -webkit-fill-available; */
|
||||
overflow: auto;
|
||||
padding: 32px;
|
||||
> .title {
|
||||
font-weight: bold;
|
||||
|
||||
@@ -1,21 +1,34 @@
|
||||
import styled from "styled-components";
|
||||
const StyledButton = styled.button`
|
||||
cursor: pointer;
|
||||
padding: 8px 16px;
|
||||
background: none;
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0px 1px 2px rgba(31, 41, 55, 0.08);
|
||||
padding: 10px 18px;
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
|
||||
border-radius: var(--br, 4px);
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
&.main {
|
||||
border: none;
|
||||
background: #1fe1f9;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #fff;
|
||||
background-color: #22ccee;
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: #06aed4;
|
||||
}
|
||||
&:focus {
|
||||
background-color: #22ccee;
|
||||
}
|
||||
&:disabled {
|
||||
background-color: #a5f0fc;
|
||||
}
|
||||
&.small {
|
||||
padding: 8px 14px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
&.danger {
|
||||
border: none;
|
||||
background: #ef4444;
|
||||
background-color: #ef4444;
|
||||
color: #fff;
|
||||
}
|
||||
&.ghost {
|
||||
|
||||
Reference in New Issue
Block a user