From d82fd3e3d807332ec376be62af3e4aa385e225dc Mon Sep 17 00:00:00 2001 From: Bruce Thomas Date: Thu, 3 Feb 2022 15:09:16 +0000 Subject: [PATCH 1/9] NPM audit fix --- package-lock.json | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 30b9add..4b96fea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1238,12 +1238,6 @@ "node": ">= 6" } }, - "node_modules/@types/auth0": { - "version": "2.34.10", - "resolved": "https://registry.npmjs.org/@types/auth0/-/auth0-2.34.10.tgz", - "integrity": "sha512-MgNPL7hEwWTPPmq9zin10kTTpdGvZLgpz7sBVK3YPyqasDGtuqJs6MiLkroKWhQq4W6lVFUQ52Wuo5QUomuOxA==", - "dev": true - }, "node_modules/@types/babel__core": { "version": "7.1.16", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz", @@ -8520,12 +8514,6 @@ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true }, - "@types/auth0": { - "version": "2.34.10", - "resolved": "https://registry.npmjs.org/@types/auth0/-/auth0-2.34.10.tgz", - "integrity": "sha512-MgNPL7hEwWTPPmq9zin10kTTpdGvZLgpz7sBVK3YPyqasDGtuqJs6MiLkroKWhQq4W6lVFUQ52Wuo5QUomuOxA==", - "dev": true - }, "@types/babel__core": { "version": "7.1.16", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz", From 66e15d770b22e758450f3d37c6ac801453a9ab41 Mon Sep 17 00:00:00 2001 From: Bruce Thomas Date: Fri, 4 Feb 2022 13:38:28 +0000 Subject: [PATCH 2/9] Refator to use 1st channel from API list --- app/src/components/Channels/ChannelBrowser.jsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/components/Channels/ChannelBrowser.jsx b/app/src/components/Channels/ChannelBrowser.jsx index a1dd1bf..9dcfde4 100644 --- a/app/src/components/Channels/ChannelBrowser.jsx +++ b/app/src/components/Channels/ChannelBrowser.jsx @@ -7,12 +7,16 @@ import ChatContainer from "../Chat/ChatContainer"; export default function ({ toggleChannelView }) { const { api } = useAuth(); const [channels, setChannels] = useState([]); - const [currentChannel, setCurrentChannel] = useState("global-welcome"); + const [currentChannel, setCurrentChannel] = useState(null); + + const [] = useState(""); useEffect(() => { const fetchChannels = async () => { const response = await api.listChannels(); - setChannels(response.channels); + const { channels } = response; + setChannels(channels); + setCurrentChannel(channels[0].name); }; fetchChannels(); }, []); @@ -23,7 +27,7 @@ export default function ({ toggleChannelView }) { toggleChannelView(); }; - return ( + return !currentChannel ? null : ( <> From 8b9dc2c47bd6de3bc6b5cea0434922b74e69f510 Mon Sep 17 00:00:00 2001 From: Bruce Thomas Date: Fri, 4 Feb 2022 13:39:16 +0000 Subject: [PATCH 3/9] Added type attribute to channel collection --- api/channels-list/index.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/api/channels-list/index.ts b/api/channels-list/index.ts index 278e21f..4dfcdaf 100644 --- a/api/channels-list/index.ts +++ b/api/channels-list/index.ts @@ -2,13 +2,17 @@ import "../startup"; import { Context, HttpRequest } from "@azure/functions"; import { authorized } from "../common/ApiRequestContext"; -type ChannelSummary = { name: string }; +type ChannelSummary = { name: string; type: string }; type ChannelListResponse = { channels: ChannelSummary[] }; export default async function (context: Context, req: HttpRequest): Promise { await authorized(context, req, () => { const channels: ChannelListResponse = { - channels: [{ name: "global-welcome" }, { name: "some-other-channel" }] + channels: [ + { name: "global-welcome", type: "public" }, + { name: "some-other-channel", type: "public" }, + { name: "utility", type: "private" } + ] }; context.res = { status: 200, body: JSON.stringify(channels) }; From ad94e4b777aea9237b85089a224bfd85771be306 Mon Sep 17 00:00:00 2001 From: Bruce Thomas Date: Fri, 4 Feb 2022 15:15:22 +0000 Subject: [PATCH 4/9] Added status callback and message UI --- app/src/components/Chat/ChatContainer.jsx | 50 ++++++++++++++++++--- app/src/components/Chat/ChatInput.jsx | 53 +++++++++++++++-------- 2 files changed, 77 insertions(+), 26 deletions(-) diff --git a/app/src/components/Chat/ChatContainer.jsx b/app/src/components/Chat/ChatContainer.jsx index c30a1ea..c45994c 100644 --- a/app/src/components/Chat/ChatContainer.jsx +++ b/app/src/components/Chat/ChatContainer.jsx @@ -6,22 +6,57 @@ import useArchive from "./../../hooks/useArchive"; import autoScrollHistory from "./autoScrollHistory"; import "./chat.css"; +const ChatInputStatus = ({ message }) => { + return <>{message}; +}; + +const formatMessage = (eventObject) => { + // coerse String to Object + return typeof eventObject === "string" // + ? { text: `${eventObject || ""}`.trim() } + : eventObject; +}; + const ChatContainer = ({ currentChannel, onChatExit }) => { const endOfChatLog = useRef(null); const [history, setHistory] = useState([]); - useEffect(() => { - setHistory([]); // Reset history on channel change - }, [currentChannel]); + // Reset history on channel change + useEffect(() => setHistory([]), [currentChannel]); const [channel] = useChannel(currentChannel, (message) => { setHistory((prev) => [...prev.slice(-199), message]); }); + const [activity] = useChannel(`${currentChannel}_activity`, (message) => { + // this sibling channel is non-persisted chatter, eg. typing indicator + const { data } = message; + const { text } = data; + + console.log("activity", text); + switch (text) { + case "done": + setStatusMessage(null); + break; + + default: + setStatusMessage("Typing ..."); + break; + } + }); + const [archive, rewind] = useArchive(currentChannel); - const sendMessage = (messageText) => { - channel.publish("message", { text: messageText }); + const [statusMessage, setStatusMessage] = React.useState(null); + + const sendMessage = (eventObject = null) => { + channel.publish("message", formatMessage(eventObject)); + setStatusMessage(null); + }; + + const sendStatus = (eventObject = null) => { + activity.publish("util_message", formatMessage(eventObject)); + setStatusMessage("typing ...", eventObject); }; autoScrollHistory(archive, endOfChatLog); @@ -42,8 +77,9 @@ const ChatContainer = ({ currentChannel, onChatExit }) => {
  • - - + + + ); }; diff --git a/app/src/components/Chat/ChatInput.jsx b/app/src/components/Chat/ChatInput.jsx index 0c40eb3..812b725 100644 --- a/app/src/components/Chat/ChatInput.jsx +++ b/app/src/components/Chat/ChatInput.jsx @@ -1,33 +1,48 @@ import React from "react"; -export const ChatInput = ({ sendMessage }) => { +let timer = null; + +export const ChatInput = ({ sendMessage, sendStatus }) => { const [message, setMessage] = React.useState(""); + const [statusMessage, setStatusMessage] = React.useState(null); + const clearStatusAfter = 5 * 1000; // miliseconds + + React.useEffect(() => { + if (timer === null) { + sendStatus("start"); + } + + clearTimeout(timer); + timer = setTimeout(() => { + setStatusMessage(null); + sendStatus("done"); + timer = null; + }, clearStatusAfter); + }, [statusMessage]); const handleSubmit = (e) => { e.preventDefault(); - if (message.trim() === "") { - return; - } + if (!`${message}`.trim()) return; sendMessage(message); - setMessage(""); + setMessage(null); }; + const handleChange = ({ target }) => setMessage(target.value); + + const handleKeydown = (e) => { + switch (e.code) { + case "Enter": + handleSubmit(e); + break; + + default: + // tar-pit for keystrokes + setStatusMessage("typing..."); + } + }; return (
    - +
    ); From 4a010e83f1ffd2be1f3bd75232d4c6a6abce8f26 Mon Sep 17 00:00:00 2001 From: Bruce Thomas Date: Fri, 4 Feb 2022 15:16:04 +0000 Subject: [PATCH 5/9] Filter private channels from channel list --- app/src/components/Channels/ChannelList.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/components/Channels/ChannelList.jsx b/app/src/components/Channels/ChannelList.jsx index b5f4fda..70987b6 100644 --- a/app/src/components/Channels/ChannelList.jsx +++ b/app/src/components/Channels/ChannelList.jsx @@ -10,7 +10,8 @@ const ChannelList = ({ channels, onChannelSelected }) => { onChannelSelected(channel.name); }; - const channelListItems = channels.map((channel) => ( + const publicChannels = channels.filter((el) => el.type === "public"); + const channelListItems = publicChannels.map((channel) => (
  • Date: Thu, 10 Feb 2022 16:29:15 +0000 Subject: [PATCH 6/9] Refactor contd. using Presence instead of util channel --- app/src/components/Chat/ChatContainer.jsx | 61 +++++++++++++++-------- app/src/components/Chat/ChatInput.jsx | 37 ++++++++------ app/src/components/Chat/chat.css | 9 +++- 3 files changed, 69 insertions(+), 38 deletions(-) diff --git a/app/src/components/Chat/ChatContainer.jsx b/app/src/components/Chat/ChatContainer.jsx index c45994c..6fd3694 100644 --- a/app/src/components/Chat/ChatContainer.jsx +++ b/app/src/components/Chat/ChatContainer.jsx @@ -7,7 +7,7 @@ import autoScrollHistory from "./autoScrollHistory"; import "./chat.css"; const ChatInputStatus = ({ message }) => { - return <>{message}; + return
    {message}
    ; }; const formatMessage = (eventObject) => { @@ -19,46 +19,63 @@ const formatMessage = (eventObject) => { const ChatContainer = ({ currentChannel, onChatExit }) => { const endOfChatLog = useRef(null); + const [archive, rewind] = useArchive(currentChannel); + const [history, setHistory] = useState([]); + const [status, setStatus] = useState([]); + const [activity, setActivity] = useState(); // Reset history on channel change useEffect(() => setHistory([]), [currentChannel]); + useEffect(() => setStatus(activity), [activity]); const [channel] = useChannel(currentChannel, (message) => { setHistory((prev) => [...prev.slice(-199), message]); }); - const [activity] = useChannel(`${currentChannel}_activity`, (message) => { - // this sibling channel is non-persisted chatter, eg. typing indicator - const { data } = message; + const sendMessage = (eventObject = null) => { + channel.publish("message", formatMessage(eventObject)); + setStatusMessage(null); + }; + + const sendStatus = (eventObject = null) => { + channel.presence.update(formatMessage(eventObject)); + // setStatusMessage("Typing ..."); + // console.log({ eventObject }); + }; + + /* Subscribe to presence update events */ + channel.presence.subscribe("update", function (member) { + const { data, clientId, connectionId } = member; const { text } = data; - console.log("activity", text); + if (text === activity) return; + + // clients on channel excluding the author as shallow copy Object + let clients = []; + + channel.presence.get((_, members) => { + const typing = clientId === member.clientId; + clients = members.map((client) => ({ ...client, typing })); + // console.log(clientId, member.clientId); + }); + switch (text) { + case "start": + console.log(text); + setActivity("Typing ..."); + break; + case "done": - setStatusMessage(null); + console.log(text); + setActivity(""); break; default: - setStatusMessage("Typing ..."); break; } }); - const [archive, rewind] = useArchive(currentChannel); - - const [statusMessage, setStatusMessage] = React.useState(null); - - const sendMessage = (eventObject = null) => { - channel.publish("message", formatMessage(eventObject)); - setStatusMessage(null); - }; - - const sendStatus = (eventObject = null) => { - activity.publish("util_message", formatMessage(eventObject)); - setStatusMessage("typing ...", eventObject); - }; - autoScrollHistory(archive, endOfChatLog); return ( @@ -78,7 +95,7 @@ const ChatContainer = ({ currentChannel, onChatExit }) => {
  • - + ); }; diff --git a/app/src/components/Chat/ChatInput.jsx b/app/src/components/Chat/ChatInput.jsx index 812b725..ae60b86 100644 --- a/app/src/components/Chat/ChatInput.jsx +++ b/app/src/components/Chat/ChatInput.jsx @@ -1,43 +1,50 @@ import React from "react"; -let timer = null; +let timer = -1; export const ChatInput = ({ sendMessage, sendStatus }) => { const [message, setMessage] = React.useState(""); - const [statusMessage, setStatusMessage] = React.useState(null); const clearStatusAfter = 5 * 1000; // miliseconds React.useEffect(() => { - if (timer === null) { - sendStatus("start"); - } + if (!message) return; + if (timer < 0) sendStatus("start"); - clearTimeout(timer); - timer = setTimeout(() => { - setStatusMessage(null); + const callback = () => { sendStatus("done"); - timer = null; - }, clearStatusAfter); - }, [statusMessage]); + console.log({ timer }); + timer = -1; + }; + + clearTimeout(timer); + timer = setTimeout(callback, clearStatusAfter); + }, [message]); const handleSubmit = (e) => { e.preventDefault(); if (!`${message}`.trim()) return; + sendMessage(message); - setMessage(null); + setMessage(""); + clearTimeout(timer); + timer = -1; + sendStatus("done"); }; - const handleChange = ({ target }) => setMessage(target.value); + const handleChange = ({ target }) => setMessage(target.value || ""); const handleKeydown = (e) => { + const passive = /^(control|arrow|shift|alt|meta|page|insert|home)/i; + if (passive.test(e.code)) return; + switch (e.code) { case "Enter": handleSubmit(e); break; default: - // tar-pit for keystrokes - setStatusMessage("typing..."); + // tarpit for keypress + break; } }; return ( diff --git a/app/src/components/Chat/chat.css b/app/src/components/Chat/chat.css index ef97d06..e15b3c2 100644 --- a/app/src/components/Chat/chat.css +++ b/app/src/components/Chat/chat.css @@ -7,7 +7,14 @@ .send { display: flex; - margin: 0 1rem 1rem; + margin: 0 0.5rem; +} + +.send-status { + color: #777777; + font-size: 0.8rem; + height: 1.5rem; + margin: 0.25rem 0.5rem 0; } .send-input { From 04d656e44b961f41a2ee356751e4cdc53bc9f5d6 Mon Sep 17 00:00:00 2001 From: Bruce Thomas Date: Mon, 14 Feb 2022 09:31:41 +0000 Subject: [PATCH 7/9] Refactor hook with useEffect --- app/src/components/Chat/ChatContainer.jsx | 15 +++++++-------- app/src/components/Chat/ChatInput.jsx | 1 + 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/components/Chat/ChatContainer.jsx b/app/src/components/Chat/ChatContainer.jsx index 6fd3694..c78567c 100644 --- a/app/src/components/Chat/ChatContainer.jsx +++ b/app/src/components/Chat/ChatContainer.jsx @@ -26,8 +26,6 @@ const ChatContainer = ({ currentChannel, onChatExit }) => { const [activity, setActivity] = useState(); // Reset history on channel change - useEffect(() => setHistory([]), [currentChannel]); - useEffect(() => setStatus(activity), [activity]); const [channel] = useChannel(currentChannel, (message) => { setHistory((prev) => [...prev.slice(-199), message]); @@ -40,12 +38,9 @@ const ChatContainer = ({ currentChannel, onChatExit }) => { const sendStatus = (eventObject = null) => { channel.presence.update(formatMessage(eventObject)); - // setStatusMessage("Typing ..."); - // console.log({ eventObject }); }; - /* Subscribe to presence update events */ - channel.presence.subscribe("update", function (member) { + const handlePresenceUpdate = (member) => { const { data, clientId, connectionId } = member; const { text } = data; @@ -57,7 +52,7 @@ const ChatContainer = ({ currentChannel, onChatExit }) => { channel.presence.get((_, members) => { const typing = clientId === member.clientId; clients = members.map((client) => ({ ...client, typing })); - // console.log(clientId, member.clientId); + console.log(typing, clientId, member.clientId); }); switch (text) { @@ -74,7 +69,11 @@ const ChatContainer = ({ currentChannel, onChatExit }) => { default: break; } - }); + }; + + useEffect(() => setHistory([]), [currentChannel]); + useEffect(() => setStatus(activity), [activity]); + useEffect(() => channel.presence.subscribe("update", handlePresenceUpdate), []); autoScrollHistory(archive, endOfChatLog); diff --git a/app/src/components/Chat/ChatInput.jsx b/app/src/components/Chat/ChatInput.jsx index ae60b86..9aae397 100644 --- a/app/src/components/Chat/ChatInput.jsx +++ b/app/src/components/Chat/ChatInput.jsx @@ -47,6 +47,7 @@ export const ChatInput = ({ sendMessage, sendStatus }) => { break; } }; + return (
    From 1a898b31fddbaf2cfc96c897169e4c6e2608bdb1 Mon Sep 17 00:00:00 2001 From: Bruce Thomas Date: Thu, 17 Feb 2022 14:55:38 +0000 Subject: [PATCH 8/9] Replaced textarea with div contenteditable --- app/src/components/Chat/ChatInput.jsx | 3 ++- app/src/components/Chat/chat.css | 14 ++++++++------ package-lock.json | 28 +++++++++++++++++++++++---- package.json | 1 + 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/app/src/components/Chat/ChatInput.jsx b/app/src/components/Chat/ChatInput.jsx index 9aae397..5238a6b 100644 --- a/app/src/components/Chat/ChatInput.jsx +++ b/app/src/components/Chat/ChatInput.jsx @@ -1,4 +1,5 @@ import React from "react"; +import ContentEditable from "react-contenteditable"; let timer = -1; @@ -50,7 +51,7 @@ export const ChatInput = ({ sendMessage, sendStatus }) => { return ( - + ); diff --git a/app/src/components/Chat/chat.css b/app/src/components/Chat/chat.css index e15b3c2..0f7e02c 100644 --- a/app/src/components/Chat/chat.css +++ b/app/src/components/Chat/chat.css @@ -7,7 +7,9 @@ .send { display: flex; + flex-direction: row; margin: 0 0.5rem; + font-size: 1rem; } .send-status { @@ -17,19 +19,19 @@ margin: 0.25rem 0.5rem 0; } -.send-input { +.send-input, +.send-message { width: 100%; padding: var(--spacer); - font-size: 1rem; font-family: inherit; + border: 1px solid black; + border-radius: 0; } - .send-button { - padding: 1rem 2rem; + padding: 0.5rem 1rem; border: 0; - font-family: inherit; - font-size: 1rem; font-weight: bold; + font-family: inherit; background-color: var(--primary); color: var(--primary-text); cursor: pointer; diff --git a/package-lock.json b/package-lock.json index 4b96fea..e1b0e92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "ansi-regex": "^5.0.1", "prop-types": "^15.7.2", "react": "^17.0.2", + "react-contenteditable": "^3.3.6", "react-dom": "^17.0.2", "react-router-dom": "^5.3.0", "tslib": "^2.3.1" @@ -3598,8 +3599,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-diff": { "version": "1.2.0", @@ -6335,6 +6335,18 @@ "node": ">=0.10.0" } }, + "node_modules/react-contenteditable": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/react-contenteditable/-/react-contenteditable-3.3.6.tgz", + "integrity": "sha512-61+Anbmzggel1sP7nwvxq3d2woD3duR5R89RoLGqKan1A+nruFIcmLjw2F+qqk70AyABls0BDKzE1vqS1UIF1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "prop-types": "^15.7.1" + }, + "peerDependencies": { + "react": ">=16.3" + } + }, "node_modules/react-dom": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", @@ -10262,8 +10274,7 @@ "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-diff": { "version": "1.2.0", @@ -12332,6 +12343,15 @@ "object-assign": "^4.1.1" } }, + "react-contenteditable": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/react-contenteditable/-/react-contenteditable-3.3.6.tgz", + "integrity": "sha512-61+Anbmzggel1sP7nwvxq3d2woD3duR5R89RoLGqKan1A+nruFIcmLjw2F+qqk70AyABls0BDKzE1vqS1UIF1g==", + "requires": { + "fast-deep-equal": "^3.1.3", + "prop-types": "^15.7.1" + } + }, "react-dom": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", diff --git a/package.json b/package.json index f755c06..02f72fa 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "ansi-regex": "^5.0.1", "prop-types": "^15.7.2", "react": "^17.0.2", + "react-contenteditable": "^3.3.6", "react-dom": "^17.0.2", "react-router-dom": "^5.3.0", "tslib": "^2.3.1" From 6e9450f3ded74b5b8625e43ff715dbd777a74252 Mon Sep 17 00:00:00 2001 From: Bruce Thomas Date: Thu, 17 Feb 2022 14:58:33 +0000 Subject: [PATCH 9/9] Fix add presence enter event to activate channel --- app/src/components/Chat/ChatContainer.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/components/Chat/ChatContainer.jsx b/app/src/components/Chat/ChatContainer.jsx index c78567c..7a4826b 100644 --- a/app/src/components/Chat/ChatContainer.jsx +++ b/app/src/components/Chat/ChatContainer.jsx @@ -37,6 +37,7 @@ const ChatContainer = ({ currentChannel, onChatExit }) => { }; const sendStatus = (eventObject = null) => { + channel.presence.enter(); channel.presence.update(formatMessage(eventObject)); }; @@ -73,7 +74,7 @@ const ChatContainer = ({ currentChannel, onChatExit }) => { useEffect(() => setHistory([]), [currentChannel]); useEffect(() => setStatus(activity), [activity]); - useEffect(() => channel.presence.subscribe("update", handlePresenceUpdate), []); + useEffect(() => channel.presence.subscribe(handlePresenceUpdate)); autoScrollHistory(archive, endOfChatLog);