diff --git a/net/server/internal/api_removeChannelTopic.go b/net/server/internal/api_removeChannelTopic.go
index bc26d3fb..9d40380a 100644
--- a/net/server/internal/api_removeChannelTopic.go
+++ b/net/server/internal/api_removeChannelTopic.go
@@ -41,8 +41,8 @@ func RemoveChannelTopic(w http.ResponseWriter, r *http.Request) {
}
// check permission
- if topicSlot.Topic.Guid != guid {
- ErrResponse(w, http.StatusUnauthorized, errors.New("not creator of topic"))
+ if act.Guid != guid && topicSlot.Topic.Guid != guid {
+ ErrResponse(w, http.StatusUnauthorized, errors.New("not creator of topic or host"))
return
}
diff --git a/net/web/src/User/Conversation/AddTopic/useAddTopic.hook.js b/net/web/src/User/Conversation/AddTopic/useAddTopic.hook.js
index b115d718..bf389265 100644
--- a/net/web/src/User/Conversation/AddTopic/useAddTopic.hook.js
+++ b/net/web/src/User/Conversation/AddTopic/useAddTopic.hook.js
@@ -1,7 +1,5 @@
import { useContext, useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
-import { addChannelTopic } from 'api/addChannelTopic';
-import { addContactChannelTopic } from 'api/addContactChannelTopic';
import { CardContext } from 'context/CardContext';
import { ChannelContext } from 'context/ChannelContext';
diff --git a/net/web/src/User/Conversation/Conversation.jsx b/net/web/src/User/Conversation/Conversation.jsx
index 998eab0c..bbd8b56a 100644
--- a/net/web/src/User/Conversation/Conversation.jsx
+++ b/net/web/src/User/Conversation/Conversation.jsx
@@ -28,7 +28,7 @@ export function Conversation() {
}, [state]);
const topicRenderer = (topic) => {
- return ()
+ return ()
}
const onSaveSubject = () => {
diff --git a/net/web/src/User/Conversation/TopicItem/TopicItem.jsx b/net/web/src/User/Conversation/TopicItem/TopicItem.jsx
index bba42057..48c874ea 100644
--- a/net/web/src/User/Conversation/TopicItem/TopicItem.jsx
+++ b/net/web/src/User/Conversation/TopicItem/TopicItem.jsx
@@ -5,10 +5,11 @@ import { VideoAsset } from './VideoAsset/VideoAsset';
import { AudioAsset } from './AudioAsset/AudioAsset';
import { ImageAsset } from './ImageAsset/ImageAsset';
import { Avatar } from 'avatar/Avatar';
-import { CommentOutlined } from '@ant-design/icons';
+import { Button } from 'antd';
+import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
import { Carousel } from 'Carousel/Carousel';
-export function TopicItem({ topic }) {
+export function TopicItem({ host, topic }) {
const { state, actions } = useTopicItem(topic);
@@ -37,6 +38,39 @@ export function TopicItem({ topic }) {
return <>>
}
+ const onEdit = () => {
+ console.log("EDIT TOPIC");
+ }
+
+ const onDelete = () => {
+ console.log("DELETE TOPIC");
+ }
+
+ const Options = () => {
+ if (state.owner) {
+ return (
+
+ );
+ }
+ if (host) {
+ return (
+
+ );
+ }
+ return <>>;
+ }
+
return (
@@ -49,6 +83,9 @@ export function TopicItem({ topic }) {
{ state.message?.text }
+
+
+
)
diff --git a/net/web/src/User/Conversation/TopicItem/TopicItem.styled.js b/net/web/src/User/Conversation/TopicItem/TopicItem.styled.js
index c848aeb2..e4e22704 100644
--- a/net/web/src/User/Conversation/TopicItem/TopicItem.styled.js
+++ b/net/web/src/User/Conversation/TopicItem/TopicItem.styled.js
@@ -15,6 +15,34 @@ export const TopicItemWrapper = styled.div`
display: flex;
flex-direction: column;
padding-left: 8px;
+ flex-grow: 1;
+
+ &:hover .options {
+ visibility: visible;
+ }
+
+ .options {
+ position: absolute;
+ top: 0;
+ right: 0;
+ visibility: hidden;
+
+ .buttons {
+ display: flex;
+ flex-direction: row;
+ border-radius: 4px;
+ background-color: #eeeeee;
+ border: 1px solid #555555;
+ margin-top: 2px;
+
+ .button {
+ font-size: 14px;
+ margin-left: 8px;
+ margin-right: 8px;
+ cursor: pointer;
+ }
+ }
+ }
.info {
display: flex;
diff --git a/net/web/src/User/Conversation/TopicItem/useTopicItem.hook.js b/net/web/src/User/Conversation/TopicItem/useTopicItem.hook.js
index f8db4630..770b468d 100644
--- a/net/web/src/User/Conversation/TopicItem/useTopicItem.hook.js
+++ b/net/web/src/User/Conversation/TopicItem/useTopicItem.hook.js
@@ -14,6 +14,7 @@ export function useTopicItem(topic) {
message: null,
created: null,
ready: false,
+ owner: false,
assets: [],
});
@@ -26,6 +27,10 @@ export function useTopicItem(topic) {
}
useEffect(() => {
+ let owner = false;
+ if (profile.state.profile.guid == topic?.data?.topicDetail.guid) {
+ owner = true;
+ }
if (!topic?.data) {
console.log("invalid topic:", topic);
@@ -56,11 +61,11 @@ export function useTopicItem(topic) {
const { guid, created } = topic.data.topicDetail;
if (profile.state.profile.guid == guid) {
const { name, handle, imageUrl } = profile.actions.getProfile();
- updateState({ name, handle, imageUrl, status, message, transform, assets, ready, created });
+ updateState({ name, handle, imageUrl, status, message, transform, assets, ready, created, owner });
}
else {
const { name, handle, imageUrl } = card.actions.getCardProfileByGuid(guid);
- updateState({ name, handle, imageUrl, status, message, transform, assets, ready, created });
+ updateState({ name, handle, imageUrl, status, message, transform, assets, ready, created, owner });
}
}
}, [profile, card, conversation, topic]);
@@ -68,6 +73,9 @@ export function useTopicItem(topic) {
const actions = {
getAssetUrl: (assetId) => {
return conversation.actions.getAssetUrl(topic?.id, assetId);
+ },
+ removeTopic: async () => {
+ return conversation.actions.removeTopic(topic.id);
}
};
diff --git a/net/web/src/VirtualList/VirtualList.jsx b/net/web/src/VirtualList/VirtualList.jsx
index c465b367..0284659a 100644
--- a/net/web/src/VirtualList/VirtualList.jsx
+++ b/net/web/src/VirtualList/VirtualList.jsx
@@ -22,6 +22,7 @@ export function VirtualList({ id, items, itemRenderer }) {
let containers = useRef([]);
let listRef = useRef();
let key = useRef(null);
+ let itemView = useRef([]);
const addSlot = (id, slot) => {
setSlots((m) => { m.set(id, slot); return new Map(m); })
@@ -59,6 +60,7 @@ export function VirtualList({ id, items, itemRenderer }) {
containers.current = [];
clearSlots();
}
+ itemView.current = items;
setItems();
}, [items, id]);
@@ -87,7 +89,7 @@ export function VirtualList({ id, items, itemRenderer }) {
const limitScroll = () => {
let view = getPlacement();
- if (view && containers.current[containers.current.length - 1].index == items.length - 1) {
+ if (view && containers.current[containers.current.length - 1].index == itemView.current.length - 1) {
if (view?.overscan?.bottom <= 0) {
if (view.position.height < viewHeight.current) {
if (scrollTop.current != view.position.top) {
@@ -116,14 +118,14 @@ export function VirtualList({ id, items, itemRenderer }) {
let view = getPlacement();
if (view) {
if (view.overscan.top < OVERSCAN) {
- if (containers.current[0].index > 0 && containers.current[0].index < items.length) {
+ if (containers.current[0].index > 0 && containers.current[0].index < itemView.current.length) {
let below = containers.current[0];
let container = {
top: below.top - (DEFAULT_ITEM_HEIGHT + 2 * GUTTER),
height: DEFAULT_ITEM_HEIGHT,
index: containers.current[0].index - 1,
- id: items[containers.current[0].index - 1].id,
- revision: items[containers.current[0].index - 1].revision,
+ id: itemView.current[containers.current[0].index - 1].id,
+ revision: itemView.current[containers.current[0].index - 1].revision,
}
containers.current.unshift(container);
addSlot(container.id, getSlot(container))
@@ -131,14 +133,14 @@ export function VirtualList({ id, items, itemRenderer }) {
}
}
if (view.overscan.bottom < OVERSCAN) {
- if (containers.current[containers.current.length - 1].index + 1 < items.length) {
+ if (containers.current[containers.current.length - 1].index + 1 < itemView.current.length) {
let above = containers.current[containers.current.length - 1];
let container = {
top: above.top + above.height + 2 * GUTTER,
height: DEFAULT_ITEM_HEIGHT,
index: containers.current[containers.current.length - 1].index + 1,
- id: items[containers.current[containers.current.length - 1].index + 1].id,
- revision: items[containers.current[containers.current.length - 1].index + 1].revision,
+ id: itemView.current[containers.current[containers.current.length - 1].index + 1].id,
+ revision: itemView.current[containers.current[containers.current.length - 1].index + 1].revision,
}
containers.current.push(container);
addSlot(container.id, getSlot(container))
@@ -239,21 +241,21 @@ export function VirtualList({ id, items, itemRenderer }) {
for (let i = 0; i < containers.current.length; i++) {
let container = containers.current[i];
- if (items.length <= container.index || items[container.index].id != container.id) {
- for (let j = i; j < containers.current.length; j++) {
+ if (itemView.current.length <= container.index || itemView.current[container.index].id != container.id) {
+ while (containers.current.length > i) {
let popped = containers.current.pop();
removeSlot(popped.id);
}
break;
}
- else if (items[container.index].revision != container.revision) {
+ else if (itemView.current[container.index].revision != container.revision) {
updateSlot(container.id, getSlot(containers.current[i]));
- containers.revision = items[container.index].revision;
+ containers.revision = itemView.current[container.index].revision;
}
}
// place first slot
- if (items.length > 0 && canvasHeight > 0) {
+ if (itemView.current.length > 0 && canvasHeight > 0) {
let view = getPlacement();
if (!view) {
let pos = canvasHeight / 2;
@@ -263,9 +265,9 @@ export function VirtualList({ id, items, itemRenderer }) {
let container = {
top: pos - DEFAULT_ITEM_HEIGHT,
height: DEFAULT_ITEM_HEIGHT,
- index: items.length - 1,
- id: items[items.length - 1].id,
- revision: items[items.length - 1].revision,
+ index: itemView.current.length - 1,
+ id: itemView.current[itemView.current.length - 1].id,
+ revision: itemView.current[itemView.current.length - 1].revision,
}
containers.current.push(container);
@@ -289,7 +291,7 @@ export function VirtualList({ id, items, itemRenderer }) {
if (typeof height !== 'undefined') {
onItemHeight(container, height);
}
- return itemRenderer(items[container.index]);
+ return itemRenderer(itemView.current[container.index]);
}}
diff --git a/net/web/src/api/removeChannelTopic.js b/net/web/src/api/removeChannelTopic.js
new file mode 100644
index 00000000..bfe0ece5
--- /dev/null
+++ b/net/web/src/api/removeChannelTopic.js
@@ -0,0 +1,8 @@
+import { checkResponse, fetchWithTimeout } from './fetchUtil';
+
+export async function removeChannelTopic(token, channelId, topicId) {
+
+ let channel = await fetchWithTimeout(`/content/channels/${channelId}/topics/${topicId}?agent=${token}`,
+ { method: 'DELETE' });
+ checkResponse(channel);
+}
diff --git a/net/web/src/api/removeContactChannelTopic.js b/net/web/src/api/removeContactChannelTopic.js
new file mode 100644
index 00000000..1d5a2efa
--- /dev/null
+++ b/net/web/src/api/removeContactChannelTopic.js
@@ -0,0 +1,8 @@
+import { checkResponse, fetchWithTimeout } from './fetchUtil';
+
+export async function removeContactChannelTopic(server, token, channelId, topicId) {
+
+ let channel = await fetchWithTimeout(`https://${server}//content/channels/${channelId}/topics/${topicId}?contact=${token}`,
+ { method: 'DELETE' });
+ checkResponse(channel);
+}
diff --git a/net/web/src/context/useCardContext.hook.js b/net/web/src/context/useCardContext.hook.js
index 022e125e..dac727cb 100644
--- a/net/web/src/context/useCardContext.hook.js
+++ b/net/web/src/context/useCardContext.hook.js
@@ -9,6 +9,7 @@ import { getCardImageUrl } from 'api/getCardImageUrl';
import { getCardProfile } from 'api/getCardProfile';
import { getCardDetail } from 'api/getCardDetail';
import { removeContactChannel } from 'api/removeContactChannel';
+import { removeContactChannelTopic } from 'api/removeContactChannelTopic';
import { addContactChannelTopic } from 'api/addContactChannelTopic';
import { setCardConnecting, setCardConnected, setCardConfirmed } from 'api/setCardStatus';
import { getCardOpenMessage } from 'api/getCardOpenMessage';
@@ -220,6 +221,12 @@ export function useCardContext() {
let node = cardProfile.node;
await removeContactChannel(node, token, channelId);
},
+ removeChannelTopic: async (cardId, channelId, topicId) => {
+ let { cardProfile, cardDetail } = cards.current.get(cardId).data;
+ let token = cardProfile.guid + '.' + cardDetail.token;
+ let node = cardProfile.node;
+ await removeContactChannelTopic(node, token, channelId, topicId);
+ },
addChannelTopic: async (cardId, channelId, message, assets) => {
let { cardProfile, cardDetail } = cards.current.get(cardId).data;
let token = cardProfile.guid + '.' + cardDetail.token;
diff --git a/net/web/src/context/useChannelContext.hook.js b/net/web/src/context/useChannelContext.hook.js
index 9790bbb6..11b7e751 100644
--- a/net/web/src/context/useChannelContext.hook.js
+++ b/net/web/src/context/useChannelContext.hook.js
@@ -4,6 +4,7 @@ import { getChannelDetail } from 'api/getChannelDetail';
import { getChannelSummary } from 'api/getChannelSummary';
import { addChannel } from 'api/addChannel';
import { removeChannel } from 'api/removeChannel';
+import { removeChannelTopic } from 'api/removeChannelTopic';
import { addChannelTopic } from 'api/addChannelTopic';
import { getChannelTopics } from 'api/getChannelTopics';
import { getChannelTopic } from 'api/getChannelTopic';
@@ -109,6 +110,9 @@ export function useChannelContext() {
removeChannel: async (channelId) => {
return await removeChannel(access.current, channelId);
},
+ removeChannelTopic: async (channelId, topicId) => {
+ return await removeChannelTopic(access.current, channelId, topicId);
+ },
addChannelTopic: async (channelId, message, assets) => {
await addChannelTopic(access.current, channelId, message, assets);
},
diff --git a/net/web/src/context/useConversationContext.hook.js b/net/web/src/context/useConversationContext.hook.js
index 1fe86005..a6cd855c 100644
--- a/net/web/src/context/useConversationContext.hook.js
+++ b/net/web/src/context/useConversationContext.hook.js
@@ -247,6 +247,15 @@ export function useConversationContext() {
return await channel.actions.removeChannel(channelId);
}
},
+ removeTopic: async (topicId) => {
+ const { cardId, channelId } = conversationId.current;
+ if (cardId) {
+ return await card.actions.removeChannelTopic(cardId, channelId, topicId);
+ }
+ else {
+ return await channel.actions.removeChannelTopic(channelId, topicId);
+ }
+ },
}
return { state, actions }