@@ -53,7 +63,7 @@ export default function Message({ data, visitor, children, ...rest }) {
{
data.body &&
}
{
@@ -82,7 +92,7 @@ export default function Message({ data, visitor, children, ...rest }) {
)}
- {!data.ReadOnly | (visitor.uid === data.user.uid) && (
+ {canComment && (
<>
@@ -96,11 +106,18 @@ export default function Message({ data, visitor, children, ...rest }) {
);
}
+/**
+ * @param {{isCode: boolean, data: {__html: string}}} props
+ */
function MessageContainer({ isCode, data }) {
return isCode ? (
);
}
-function Tags({ data, user, ...rest }) {
+/**
+ * Tags component
+ * @param {{user: import('../api').User, data: string[]}} props
+ */
+function Tags({ data, user }) {
return data.length > 0 && (
{
@@ -114,7 +131,7 @@ function Tags({ data, user, ...rest }) {
const TagsList = React.memo(Tags);
-function Recommends({ forMessage, ...rest }) {
+function Recommends({ forMessage }) {
const { likes, recommendations } = forMessage;
return recommendations && recommendations.length > 0 && (
{'♡ by '}
@@ -131,20 +148,3 @@ function Recommends({ forMessage, ...rest }) {
}
const Recommendations = React.memo(Recommends);
-
-Message.propTypes = {
- data: MessageType,
- visitor: UserType.isRequired,
- children: PropTypes.node
-};
-
-MessageContainer.propTypes = {
- isCode: PropTypes.bool.isRequired,
- data: PropTypes.object.isRequired
-};
-
-Tags.propTypes = {
- user: UserType.isRequired,
- data: PropTypes.array
-};
-
diff --git a/vnext/src/ui/MessageInput.js b/vnext/src/ui/MessageInput.js
index 63c2ee79..bc9ddd54 100644
--- a/vnext/src/ui/MessageInput.js
+++ b/vnext/src/ui/MessageInput.js
@@ -1,7 +1,4 @@
import React, { useState, useEffect, useRef } from 'react';
-import PropTypes from 'prop-types';
-
-import { MessageType } from './Types';
import Icon from './Icon';
import Button from './Button';
@@ -9,7 +6,10 @@ import Button from './Button';
import UploadButton from './UploadButton';
-// StackOverflow-driven development: https://stackoverflow.com/a/10158364/1097384
+/**
+ * StackOverflow-driven development: https://stackoverflow.com/a/10158364/1097384
+ * @param {HTMLTextAreaElement} el
+ */
function moveCaretToEnd(el) {
if (typeof el.selectionStart == 'number') {
el.selectionStart = el.selectionEnd = el.value.length;
@@ -21,8 +21,25 @@ function moveCaretToEnd(el) {
}
}
+/**
+ * @typedef {Object} MessageInputProps
+ * @property {string} text
+ * @property {import('../api').Message} data
+ * @property {function} onSend
+ */
+
+/**
+ * MessageInput
+ * @param {HTMLTextAreaElement & MessageInputProps} props
+ */
export default function MessageInput({ text, data, rows, children, onSend }) {
+ /**
+ * @type {React.MutableRefObject
}
+ */
let textareaRef = useRef();
+ /**
+ * @type {React.MutableRefObject}
+ */
let fileinput = useRef();
let updateFocus = () => {
@@ -114,11 +131,3 @@ const textInputStyle = {
outline: 'none',
padding: '4px'
};
-
-MessageInput.propTypes = {
- children: PropTypes.node,
- data: MessageType.isRequired,
- onSend: PropTypes.func.isRequired,
- rows: PropTypes.string,
- text: PropTypes.string
-};
diff --git a/vnext/src/ui/NavigationIcon.js b/vnext/src/ui/NavigationIcon.js
index 0a22ac57..9594d61f 100644
--- a/vnext/src/ui/NavigationIcon.js
+++ b/vnext/src/ui/NavigationIcon.js
@@ -5,6 +5,15 @@ import Icon from './Icon';
import './NavigationIcon.css';
+/**
+ * @typedef {Object} NavigationIconProps
+ * @property {(event: React.MouseEvent) => void} onToggle
+ */
+
+ /**
+ * Navigation icon
+ * @param {NavigationIconProps} props
+ */
function NavigationIcon(props) {
return (
diff --git a/vnext/src/ui/PM.js b/vnext/src/ui/PM.js
index 08db523f..a25580e5 100644
--- a/vnext/src/ui/PM.js
+++ b/vnext/src/ui/PM.js
@@ -1,7 +1,5 @@
import React from 'react';
-import { UserType, MessageType } from './Types';
-
import Avatar from './Avatar';
import { format } from '../utils/embed';
import { bubbleStyle, chatItemStyle } from './helpers/BubbleStyle';
@@ -13,7 +11,7 @@ function PM(props) {
@@ -21,8 +19,9 @@ function PM(props) {
}
export default React.memo(PM);
-
+/*
PM.propTypes = {
chat: MessageType.isRequired,
visitor: UserType.isRequired
};
+*/
diff --git a/vnext/src/ui/Post.js b/vnext/src/ui/Post.js
index 662f9f78..99189632 100644
--- a/vnext/src/ui/Post.js
+++ b/vnext/src/ui/Post.js
@@ -1,8 +1,5 @@
import React, { useState } from 'react';
-import ReactRouterPropTypes from 'react-router-prop-types';
-import { UserType } from './Types';
-
import qs from 'qs';
import Button from './Button';
@@ -49,9 +46,10 @@ export default function Post({ location, visitor, history }) {
);
}
-
+/*
Post.propTypes = {
location: ReactRouterPropTypes.location,
history: ReactRouterPropTypes.history.isRequired,
visitor: UserType
};
+*/
\ No newline at end of file
diff --git a/vnext/src/ui/Settings.js b/vnext/src/ui/Settings.js
index cf6926f8..61ac91d4 100644
--- a/vnext/src/ui/Settings.js
+++ b/vnext/src/ui/Settings.js
@@ -1,17 +1,13 @@
import React, { useState, useEffect, useRef } from 'react';
-import PropTypes from 'prop-types';
-import ReactRouterPropTypes from 'react-router-prop-types';
import { me, updateAvatar } from '../api';
-import { UserType } from './Types';
-
import Button from './Button';
import Icon from './Icon';
import UploadButton from './UploadButton';
import Avatar from './Avatar';
-function ChangeAvatarForm({ visitor }) {
+function ChangeAvatarForm({ visitor, onChange }) {
const [avatar, setAvatar] = useState('');
const [preview, setPreview] = useState();
const avatarInput = useRef();
@@ -37,7 +33,7 @@ function ChangeAvatarForm({ visitor }) {
updateAvatar(avatarInput.current.files[0]).then(() => {
avatarChanged('');
me().then(visitor => {
- this.props.onChange(visitor);
+ onChange(visitor);
});
});
};
@@ -52,185 +48,173 @@ function ChangeAvatarForm({ visitor }) {
);
}
-
+/*
ChangeAvatarForm.propTypes = {
- visitor: UserType.isRequired
+ visitor: UserType.isRequired,
+ onChange: PropTypes.func.isRequired
};
+*/
+export default function Settings({ visitor, onChange }) {
-export default class Settings extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- settings: {
- },
- me: {}
- };
- }
- componentDidMount() {
- me().then(visitor => {
- this.setState({
- me: visitor
- });
- });
- }
-
- passwordChanged = (event) => {
- let newState = update(this.state, {
- settings: { password: { $set: event.target.value } }
- });
- this.setState(newState);
- }
- onSubmitPassword = (event) => {
+ let passwordChanged = (event) => {
+ console.log('password changed');
+ };
+ let onSubmitPassword = (event) => {
if (event.preventDefault) {
event.preventDefault();
}
console.log('password update');
- }
- emailChanged = (event) => {
- let newState = update(this.state, {
- me: { activeEmail: { $set: event.target.value } }
- });
- this.setState(newState);
+ };
+ let emailChanged = (event) => {
console.log('email update');
- }
- disableTelegram = () => {
+ };
+ let disableTelegram = () => {
console.log('telegram disable');
- }
- disableFacebook = (event) => {
+ };
+ let disableFacebook = (event) => {
if (event.preventDefault) {
event.preventDefault();
}
console.log('facebook disable');
- }
- enableFacebook = (event) => {
+ };
+ let enableFacebook = (event) => {
if (event.preventDefault) {
event.preventDefault();
}
console.log('facebook enable');
- }
- disableTwitter = () => {
+ };
+ let disableTwitter = () => {
console.log('twitter disable');
- }
+ };
+ let deleteJid = () => {
- render() {
- const { me, settings } = this.state;
- return (
-
-
-
+ );
}
-
+/*
Settings.propTypes = {
visitor: UserType.isRequired,
onChange: PropTypes.func.isRequired
};
+*/
diff --git a/vnext/src/ui/Thread.js b/vnext/src/ui/Thread.js
index f965e80c..ae5e4f3c 100644
--- a/vnext/src/ui/Thread.js
+++ b/vnext/src/ui/Thread.js
@@ -1,8 +1,4 @@
import React, { useEffect, useState, useRef, useCallback } from 'react';
-import PropTypes from 'prop-types';
-
-import ReactRouterPropTypes from 'react-router-prop-types';
-import { UserType, MessageType } from './Types';
import Message from './Message';
import MessageInput from './MessageInput';
@@ -101,7 +97,7 @@ function Comment({ msg, draft, visitor, active, setActive, onStartEditing, postC
);
}
-
+/*
Comment.propTypes = {
msg: MessageType.isRequired,
draft: PropTypes.string.isRequired,
@@ -110,7 +106,7 @@ Comment.propTypes = {
setActive: PropTypes.func.isRequired,
onStartEditing: PropTypes.func.isRequired,
postComment: PropTypes.func.isRequired
-};
+};*/
export default function Thread(props) {
const [message, setMessage] = useState((props.location.state || {}).msg || {});
@@ -118,22 +114,8 @@ export default function Thread(props) {
const [loading, setLoading] = useState(false);
const [active, setActive] = useState(0);
const [editing, setEditing] = useState({});
- useEffect(() => {
- setActive(0);
- loadReplies();
- }, [loadReplies]);
- useEffect(() => {
- if (props.connection.addEventListener && message.mid) {
- props.connection.addEventListener('msg', onReply);
- }
- return () => {
- if (props.connection.removeEventListener && message.mid) {
- props.connection.removeEventListener('msg', onReply);
- }
- };
- }, [props.connection, message.mid, onReply]);
- let loadReplies = useCallback(() => {
+ let loadReplies = () => {
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
@@ -156,7 +138,7 @@ export default function Thread(props) {
).catch(ex => {
console.log(ex);
});
- }, [props.visitor, props.match.params]);
+ };
let onReply = useCallback((json) => {
const msg = JSON.parse(json.data);
if (msg.mid == message.mid) {
@@ -165,6 +147,7 @@ export default function Thread(props) {
});
}
}, [message]);
+
let postComment = (template) => {
const { mid, rid, body, attach } = template;
let commentAction = editing.rid ? update(mid, editing.rid, body) : comment(mid, rid, body, attach);
@@ -180,6 +163,21 @@ export default function Thread(props) {
setEditing(reply);
};
+ useEffect(() => {
+ setActive(0);
+ loadReplies();
+ }, []);
+ useEffect(() => {
+ if (props.connection.addEventListener && message.mid) {
+ props.connection.addEventListener('msg', onReply);
+ }
+ return () => {
+ if (props.connection.removeEventListener && message.mid) {
+ props.connection.removeEventListener('msg', onReply);
+ }
+ };
+ }, [props.connection, message.mid, onReply]);
+
const loaders = Math.min(message.replies || 0, 10);
return (
<>
@@ -214,11 +212,11 @@ export default function Thread(props) {
const linkStyle = {
cursor: 'pointer'
};
-
+/*
Thread.propTypes = {
location: ReactRouterPropTypes.location,
history: ReactRouterPropTypes.history,
match: ReactRouterPropTypes.match,
visitor: UserType.isRequired,
connection: PropTypes.object.isRequired,
-};
+};*/
diff --git a/vnext/src/ui/Types.js b/vnext/src/ui/Types.js
deleted file mode 100644
index 9bf7b513..00000000
--- a/vnext/src/ui/Types.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import PropTypes from 'prop-types';
-
-export const UserType = PropTypes.shape({
- uid: PropTypes.number.isRequired,
- uname: PropTypes.string,
- avatar: PropTypes.string,
- uri: PropTypes.string
-});
-
-export const MessageType = PropTypes.shape({
- mid: PropTypes.number,
- user: UserType,
- timestamp: PropTypes.string.isRequired,
- body: PropTypes.string
-});
diff --git a/vnext/src/ui/UploadButton.js b/vnext/src/ui/UploadButton.js
index 73cbbfcf..28a1f340 100644
--- a/vnext/src/ui/UploadButton.js
+++ b/vnext/src/ui/UploadButton.js
@@ -1,8 +1,18 @@
import React from 'react';
-import PropTypes from 'prop-types';
import Icon from './Icon';
+/**
+ * @typedef {Object} UploadButtonProps
+ * @property {string} value
+ * @property {React.MutableRefObject
} inputRef
+ * @property {function} onChange
+ */
+
+/**
+ * Upload button
+ * @param {UploadButtonProps} props
+ */
export default function UploadButton(props) {
let openfile = () => {
const input = props.inputRef.current;
@@ -12,6 +22,10 @@ export default function UploadButton(props) {
input.click();
}
};
+
+ /**
+ * @param {React.ChangeEvent} event
+ */
let attachmentChanged = (event) => {
props.onChange(event.target.value);
};
@@ -26,12 +40,6 @@ export default function UploadButton(props) {
);
}
-UploadButton.propTypes = {
- value: PropTypes.string.isRequired,
- onChange: PropTypes.func.isRequired,
- inputRef: PropTypes.shape({ current: PropTypes.instanceOf(Element) })
-};
-
const inactiveStyle = {
cursor: 'pointer',
color: '#888'
diff --git a/vnext/src/ui/UserInfo.css b/vnext/src/ui/UserInfo.css
index 92cfdb6c..3e692a86 100644
--- a/vnext/src/ui/UserInfo.css
+++ b/vnext/src/ui/UserInfo.css
@@ -4,8 +4,8 @@
margin: 12px;
}
.info-avatar img {
- max-height: 24px;
- max-width: 24px;
+ max-height: 36px;
+ max-width: 36px;
padding: 6px;
vertical-align: middle;
}
diff --git a/vnext/src/ui/UserInfo.js b/vnext/src/ui/UserInfo.js
index 0d06d134..faa2ebd6 100644
--- a/vnext/src/ui/UserInfo.js
+++ b/vnext/src/ui/UserInfo.js
@@ -1,9 +1,6 @@
import React, { useState, useEffect } from 'react';
-import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
-import { UserType } from './Types';
-
import { info, fetchUserUri } from '../api';
import Avatar from './Avatar';
@@ -14,8 +11,20 @@ import './UserInfo.css';
let isMounted;
+/**
+ * Wrapper for dumb VSCode
+ * @param {import('../api').User} user
+ */
+function useUserState(user) {
+ return useState(user);
+}
+
+/**
+ * User info component
+ * @param {{user: string, onUpdate?: function, children?: Element}} props
+ */
export default function UserInfo(props) {
- const [user, setUser] = useState({ uname: props.user, uid: 0 });
+ const [user, setUser] = useUserState({ uname: props.user, uid: 0 });
const { onUpdate } = props;
useEffect(() => {
isMounted = true;
@@ -63,6 +72,10 @@ export default function UserInfo(props) {
);
}
+/**
+ * User summary component
+ * @param {{user: import('../api').User}} props
+ */
function Summary({ user }) {
const readUrl = `/${user.uname}/friends`;
const readersUrl = `/${user.uname}/readers`;
@@ -78,12 +91,13 @@ function Summary({ user }) {
);
}
-Summary.propTypes = {
- user: UserType.isRequired
-};
-
const UserSummary = React.memo(Summary);
+
+/**
+ * Link to user
+ * @param {{ user: import('../api').User}} props
+ */
export function UserLink(props) {
const [user, setUser] = useState(props.user);
useEffect(() => {
@@ -105,13 +119,3 @@ export function UserLink(props) {
:
{user.uname}
);
}
-
-UserInfo.propTypes = {
- user: PropTypes.string.isRequired,
- onUpdate: PropTypes.func,
- children: PropTypes.node
-};
-
-UserLink.propTypes = {
- user: UserType.isRequired
-};
diff --git a/vnext/src/ui/Users.js b/vnext/src/ui/Users.js
index a10bba7f..4c09318f 100644
--- a/vnext/src/ui/Users.js
+++ b/vnext/src/ui/Users.js
@@ -1,18 +1,28 @@
import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-import ReactRouterPropTypes from 'react-router-prop-types';
import UserInfo from './UserInfo';
import Avatar from './Avatar';
+/**
+ * Friends feed
+ * @param {{match: import('react-router').match }} match
+ */
export function Friends({ match }) {
return ;
}
+/**
+ * Readers feed
+ * @param {{match: import('react-router').match }} match
+ */
export function Readers({ match }) {
return ;
}
+/**
+ * UserInfo list component
+ * @param {{user: import('../api').User, prop: string}} props
+ */
function Users(props) {
const [user, setUser] = useState({ uid: 0, uname: props.user });
return (
@@ -28,17 +38,3 @@ function Users(props) {
);
}
-
-
-Friends.propTypes = {
- match: ReactRouterPropTypes.match.isRequired
-};
-
-Readers.propTypes = {
- match: ReactRouterPropTypes.match.isRequired
-};
-
-Users.propTypes = {
- user: PropTypes.string.isRequired,
- prop: PropTypes.string.isRequired
-};
diff --git a/vnext/src/ui/helpers/BubbleStyle.js b/vnext/src/ui/helpers/BubbleStyle.js
index f44f726e..f8b0f4eb 100644
--- a/vnext/src/ui/helpers/BubbleStyle.js
+++ b/vnext/src/ui/helpers/BubbleStyle.js
@@ -1,3 +1,8 @@
+/**
+ * @param {import('../../api').User} me
+ * @param {import('../../api').Message} msg
+ * @returns {React.CSSProperties}
+ */
export function bubbleStyle(me, msg) {
const isMe = me.uid === msg.user.uid;
const color = isMe ? '#fff' : '#222';
@@ -10,6 +15,11 @@ export function bubbleStyle(me, msg) {
};
}
+/**
+ * @param {import('../../api').User} me
+ * @param {import('../../api').Message} msg
+ * @returns {React.CSSProperties}
+ */
export function chatItemStyle(me, msg) {
const isMe = me.uid === msg.user.uid;
const alignment = isMe ? 'flex-end' : 'flex-start';
--
cgit v1.2.3