aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Vitaly Takmazov2019-06-11 14:56:08 +0300
committerGravatar Vitaly Takmazov2023-01-13 10:37:55 +0300
commitee5f3a4a78cd9a4cc2ed259ce599db95765f24ce (patch)
tree7e0243d335af5b93c49d5d29ce80988bbed8b220
parentbe48cd1cccacc0cf5b0f6c84455ab54a6a7bf672 (diff)
Message editing
-rw-r--r--vnext/src/api/index.js8
-rw-r--r--vnext/src/ui/Message.js8
-rw-r--r--vnext/src/ui/MessageInput.js9
-rw-r--r--vnext/src/ui/Post.js13
-rw-r--r--vnext/src/ui/Thread.js61
-rw-r--r--vnext/src/ui/__tests__/MessageInput-test.js59
6 files changed, 99 insertions, 59 deletions
diff --git a/vnext/src/api/index.js b/vnext/src/api/index.js
index 2332ed48..e6b1d2ef 100644
--- a/vnext/src/api/index.js
+++ b/vnext/src/api/index.js
@@ -75,6 +75,14 @@ export function comment(mid, rid, body, attach) {
return client.post('/api/comment', form);
}
+export function update(mid, rid, body) {
+ let form = new FormData();
+ form.append('mid', mid);
+ form.append('rid', rid);
+ form.append('body', body);
+ return client.post('/api/update', form);
+}
+
export function updateAvatar(newAvatar) {
let form = new FormData();
form.append('avatar', newAvatar);
diff --git a/vnext/src/ui/Message.js b/vnext/src/ui/Message.js
index eb008bfe..fd225282 100644
--- a/vnext/src/ui/Message.js
+++ b/vnext/src/ui/Message.js
@@ -38,7 +38,15 @@ export default function Message({ data, visitor, children, ...rest }) {
{moment.utc(data.timestamp).fromNow()}
</time>
</Link>
+ {
+ visitor.uid == data.user.uid &&
+ <>
+ <span>&nbsp;&middot;&nbsp;</span>
+ <Link to={{ pathname: '/post', state: { draft: data }}}>Edit</Link>
+ </>
+ }
</div>
+
</Avatar>
<TagsList user={data.user} data={data.tags || []} />
</header>
diff --git a/vnext/src/ui/MessageInput.js b/vnext/src/ui/MessageInput.js
index e4988d59..fc3596cc 100644
--- a/vnext/src/ui/MessageInput.js
+++ b/vnext/src/ui/MessageInput.js
@@ -21,9 +21,8 @@ export default function MessageInput({ text, data, rows, children, onSend }) {
}
};
useEffect(() => {
- textareaRef.current.value = text || '';
updateFocus();
- }, [text]);
+ }, []);
let handleCtrlEnter = (event) => {
if (event.ctrlKey && (event.charCode == 10 || event.charCode == 13)) {
@@ -37,7 +36,9 @@ export default function MessageInput({ text, data, rows, children, onSend }) {
el.style.height = `${height + offset}px`;
};
const [attach, setAttach] = useState('');
- const [formState, { textarea }] = useFormState();
+ const [formState, { textarea }] = useFormState({
+ body: text
+ });
let uploadValueChanged = (attach) => {
setAttach(attach);
};
@@ -62,7 +63,7 @@ export default function MessageInput({ text, data, rows, children, onSend }) {
<form className="msg-comment-target" style={{ padding: '12px' }} onSubmit={onSubmit}>
<div style={commentStyle}>
<textarea onChange={textChanged} onKeyPress={handleCtrlEnter}
- ref={textareaRef} style={textInputStyle} value={formState.values.body}
+ ref={textareaRef} style={textInputStyle}
rows={rows || '1'} placeholder={children} {...textarea('body')} />
<div style={inputBarStyle}>
<UploadButton inputRef={fileinput} value={attach} onChange={uploadValueChanged} />
diff --git a/vnext/src/ui/Post.js b/vnext/src/ui/Post.js
index 3dc23613..dc1c7a9d 100644
--- a/vnext/src/ui/Post.js
+++ b/vnext/src/ui/Post.js
@@ -7,23 +7,25 @@ import qs from 'qs';
import MessageInput from './MessageInput';
-import { post } from '../api';
+import { post, update } from '../api';
-function PostComponent(props) {
+function PostComponent({ location, visitor, history }) {
+ let draftMessage = (location.state || {}).draft || {};
let params = qs.parse(window.location.search.substring(1));
let postMessage = (template) => {
const { attach, body } = template;
- post(body, attach)
+ const postAction = draftMessage.mid ? update(draftMessage.mid, 0, body) : post(body, attach);
+ postAction
.then(response => {
if (response.status === 200) {
const msg = response.data.newMessage;
- this.props.history.push(`/${this.props.visitor.uname}/${msg.mid}`);
+ history.push(`/${visitor.uname}/${msg.mid}`);
}
}).catch(console.log);
};
return (
<div className="msg-cont">
- <MessageInput rows="7" text={params.body || ''} data={{ mid: 0, timestamp: '0' }} onSend={postMessage}>
+ <MessageInput rows="7" text={params.body || draftMessage.body || ''} data={{ mid: 0, timestamp: '0' }} onSend={postMessage}>
*weather It is very cold today!
</MessageInput>
</div>
@@ -33,6 +35,7 @@ function PostComponent(props) {
export default memo(PostComponent);
PostComponent.propTypes = {
+ location: ReactRouterPropTypes.location,
history: ReactRouterPropTypes.history.isRequired,
visitor: UserType
};
diff --git a/vnext/src/ui/Thread.js b/vnext/src/ui/Thread.js
index 30fa0723..6cbb5188 100644
--- a/vnext/src/ui/Thread.js
+++ b/vnext/src/ui/Thread.js
@@ -12,13 +12,13 @@ import Button from './Button';
import { format, embedUrls } from '../utils/embed';
-import { getMessages, comment, markReadTracker, fetchUserUri } from '../api';
+import { getMessages, comment, update, markReadTracker, fetchUserUri, updateAvatar } from '../api';
import './Thread.css';
let isMounted;
-function Comment({ msg, visitor, active, setActive, postComment }) {
+function Comment({ msg, draft, visitor, active, setActive, onStartEditing, postComment }) {
const embedRef = useRef();
const msgRef = useRef();
const [author, setAuthor] = useState(msg.user);
@@ -33,16 +33,16 @@ function Comment({ msg, visitor, active, setActive, postComment }) {
useEffect(() => {
isMounted = true;
if (author.uri) {
- fetchUserUri(author.uri).then(response => {
- if (isMounted) {
- setAuthor(response.data);
- }
- });
+ fetchUserUri(author.uri).then(response => {
+ if (isMounted) {
+ setAuthor(response.data);
+ }
+ });
}
return () => {
- isMounted = false;
+ isMounted = false;
};
-}, [author.uri]);
+ }, [author.uri]);
return (
<div>
<div className="msg-header" style={{ padding: '6px', display: 'flex', alignItems: 'flex-start' }}>
@@ -59,6 +59,13 @@ function Comment({ msg, visitor, active, setActive, postComment }) {
visitor.uid > 0 ? (
<>
{active === msg.rid || <span style={linkStyle} onClick={() => setActive(msg.rid)}>Reply</span>}
+ {
+ visitor.uid == msg.user.uid &&
+ <>
+ <span>&nbsp;&middot;&nbsp;</span>
+ <span style={linkStyle} onClick={() => onStartEditing(msg)}>Edit</span>
+ </>
+ }
</>
) : (
<>
@@ -77,18 +84,18 @@ function Comment({ msg, visitor, active, setActive, postComment }) {
</div>
}
</div>
- {
- msg.photo &&
- <div className="msg-media">
- <a href={`//i.juick.com/p/${msg.mid}-${msg.rid}.${msg.attach}`} data-fname={`${msg.mid}-${msg.rid}.${msg.attach}`}>
- <img src={`//i.juick.com/p/${msg.mid}-${msg.rid}.${msg.attach}`} alt="" />
- </a>
- </div>
- }
+ {
+ msg.photo &&
+ <div className="msg-media">
+ <a href={`//i.juick.com/p/${msg.mid}-${msg.rid}.${msg.attach}`} data-fname={`${msg.mid}-${msg.rid}.${msg.attach}`}>
+ <img src={`//i.juick.com/p/${msg.mid}-${msg.rid}.${msg.attach}`} alt="" />
+ </a>
+ </div>
+ }
<div className="embedContainer" ref={embedRef} />
<div className="msg-links">
{
- active === msg.rid && <MessageInput data={msg} onSend={postComment}>Write a comment...</MessageInput>
+ active === msg.rid && <MessageInput data={msg} text={draft || ''} onSend={postComment}>Write a comment...</MessageInput>
}
</div>
</div>
@@ -97,9 +104,11 @@ function Comment({ msg, visitor, active, setActive, postComment }) {
Comment.propTypes = {
msg: MessageType.isRequired,
+ draft: PropTypes.string.isRequired,
visitor: UserType.isRequired,
active: PropTypes.number.isRequired,
setActive: PropTypes.func.isRequired,
+ onStartEditing: PropTypes.func.isRequired,
postComment: PropTypes.func.isRequired
};
@@ -108,6 +117,7 @@ export default function Thread(props) {
const [replies, setReplies] = useState([]);
const [loading, setLoading] = useState(false);
const [active, setActive] = useState(0);
+ const [editing, setEditing] = useState({});
useEffect(() => {
setActive(0);
loadReplies();
@@ -157,19 +167,26 @@ export default function Thread(props) {
}, [message]);
let postComment = (template) => {
const { mid, rid, body, attach } = template;
- comment(mid, rid, body, attach).then(res => {
+ let commentAction = editing.rid ? update(mid, editing.rid, body) : comment(mid, rid, body, attach);
+ commentAction.then(res => {
+ setEditing({});
loadReplies();
})
.catch(console.log);
};
+ let startEditing = (reply) => {
+ setActive(reply.replyto);
+ setEditing(reply);
+ };
+
const loaders = Math.min(message.replies || 0, 10);
return (
<>
{
message.mid ? (
<Message data={message} visitor={props.visitor}>
- {active === (message.rid || 0) && <MessageInput data={message} onSend={postComment}>Write a comment...</MessageInput>}
+ {active === (message.rid || 0) && <MessageInput data={message} text={editing.body || ''} onSend={postComment}>Write a comment...</MessageInput>}
</Message>
) : (
<Spinner />
@@ -179,7 +196,7 @@ export default function Thread(props) {
{
!loading ? replies.map((msg) => (
<li id={msg.rid} key={msg.rid}>
- <Comment msg={msg} visitor={props.visitor} active={active} setActive={setActive} postComment={postComment} />
+ <Comment msg={msg} draft={msg.rid === editing.replyto && editing.body} visitor={props.visitor} active={active} setActive={setActive} onStartEditing={startEditing} postComment={postComment} />
</li>
)) : (
<>
@@ -201,5 +218,5 @@ Thread.propTypes = {
history: ReactRouterPropTypes.history,
match: ReactRouterPropTypes.match,
visitor: UserType.isRequired,
- connection: PropTypes.object.isRequired
+ connection: PropTypes.object.isRequired,
};
diff --git a/vnext/src/ui/__tests__/MessageInput-test.js b/vnext/src/ui/__tests__/MessageInput-test.js
index 7ac69ed0..9603d1fe 100644
--- a/vnext/src/ui/__tests__/MessageInput-test.js
+++ b/vnext/src/ui/__tests__/MessageInput-test.js
@@ -12,31 +12,35 @@ const testMessage = {
to: {}
};
-window.matchMedia = window.matchMedia || function () {
+window.matchMedia = window.matchMedia || function() {
return {
matches: true,
- addListener: function () { },
- removeListener: function () { }
+ addListener: function() { },
+ removeListener: function() { }
};
};
+function createMessageInput(data, onFocus, onSend, draft) {
+ return create(<MessageInput data={data} onSend={onSend} text={draft} />, {
+ createNodeMock: (element) => {
+ if (element.type === 'textarea') {
+ // mock a focus function
+ return {
+ focus: onFocus,
+ style: {}
+ };
+ }
+ return null;
+ }
+ });
+}
+
it('Gives immediate focus on to textarea on load', () => {
let focused = false;
act(() => {
- create(<MessageInput data={testMessage} onSend={() => { }} />, {
- createNodeMock: (element) => {
- if (element.type === 'textarea') {
- // mock a focus function
- return {
- focus: () => {
- focused = true;
- },
- style: {}
- };
- }
- return null;
- }
- });
+ createMessageInput(testMessage, () => {
+ focused = true;
+ }, () => { });
});
expect(focused).toEqual(true, 'textarea was not focused');
});
@@ -46,17 +50,7 @@ it('Submits on ctrl-enter', () => {
const onSend = jest.fn();
var messageInput = null;
act(() => {
- messageInput = create(<MessageInput data={testMessage} onSend={onSend} />, {
- createNodeMock: (element) => {
- if (element.type === 'textarea') {
- return {
- focus: () => { },
- style: {}
- };
- }
- return null;
- }
- });
+ messageInput = createMessageInput(testMessage, () => {}, onSend);
});
let textarea = messageInput.root.findByType('textarea');
act(() => {
@@ -93,3 +87,12 @@ it('Submits on ctrl-enter', () => {
});
expect(textarea.props.value).toEqual('', 'Value should be cleared after submit');
});
+
+it('Show draft text', () => {
+ var messageInput;
+ act(() => {
+ messageInput = createMessageInput(testMessage, () => {}, () => {}, 'yo');
+ });
+ let textarea = messageInput.root.findByType('textarea');
+ expect(textarea.props.value).toEqual('yo', 'Value should match draft');
+});