mirror of
https://github.com/balzack/databag.git
synced 2025-02-14 20:49:16 +00:00
support deleting topics
This commit is contained in:
parent
04c965847b
commit
08517c4abd
@ -41,8 +41,8 @@ func RemoveChannelTopic(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check permission
|
// check permission
|
||||||
if topicSlot.Topic.Guid != guid {
|
if act.Guid != guid && topicSlot.Topic.Guid != guid {
|
||||||
ErrResponse(w, http.StatusUnauthorized, errors.New("not creator of topic"))
|
ErrResponse(w, http.StatusUnauthorized, errors.New("not creator of topic or host"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { useContext, useState, useEffect } from 'react';
|
import { useContext, useState, useEffect } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { addChannelTopic } from 'api/addChannelTopic';
|
|
||||||
import { addContactChannelTopic } from 'api/addContactChannelTopic';
|
|
||||||
import { CardContext } from 'context/CardContext';
|
import { CardContext } from 'context/CardContext';
|
||||||
import { ChannelContext } from 'context/ChannelContext';
|
import { ChannelContext } from 'context/ChannelContext';
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ export function Conversation() {
|
|||||||
}, [state]);
|
}, [state]);
|
||||||
|
|
||||||
const topicRenderer = (topic) => {
|
const topicRenderer = (topic) => {
|
||||||
return (<TopicItem topic={topic} />)
|
return (<TopicItem host={state.cardId == null} topic={topic} />)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSaveSubject = () => {
|
const onSaveSubject = () => {
|
||||||
|
@ -5,10 +5,11 @@ import { VideoAsset } from './VideoAsset/VideoAsset';
|
|||||||
import { AudioAsset } from './AudioAsset/AudioAsset';
|
import { AudioAsset } from './AudioAsset/AudioAsset';
|
||||||
import { ImageAsset } from './ImageAsset/ImageAsset';
|
import { ImageAsset } from './ImageAsset/ImageAsset';
|
||||||
import { Avatar } from 'avatar/Avatar';
|
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';
|
import { Carousel } from 'Carousel/Carousel';
|
||||||
|
|
||||||
export function TopicItem({ topic }) {
|
export function TopicItem({ host, topic }) {
|
||||||
|
|
||||||
const { state, actions } = useTopicItem(topic);
|
const { state, actions } = useTopicItem(topic);
|
||||||
|
|
||||||
@ -37,6 +38,39 @@ export function TopicItem({ topic }) {
|
|||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onEdit = () => {
|
||||||
|
console.log("EDIT TOPIC");
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDelete = () => {
|
||||||
|
console.log("DELETE TOPIC");
|
||||||
|
}
|
||||||
|
|
||||||
|
const Options = () => {
|
||||||
|
if (state.owner) {
|
||||||
|
return (
|
||||||
|
<div class="buttons">
|
||||||
|
<div class="button" onClick={() => onEdit()}>
|
||||||
|
<EditOutlined />
|
||||||
|
</div>
|
||||||
|
<div class="button" onClick={() => actions.removeTopic()}>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (host) {
|
||||||
|
return (
|
||||||
|
<div class="buttons">
|
||||||
|
<div class="button" onClick={() => actions.removeTopic()}>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TopicItemWrapper>
|
<TopicItemWrapper>
|
||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
@ -49,6 +83,9 @@ export function TopicItem({ topic }) {
|
|||||||
</div>
|
</div>
|
||||||
<Carousel ready={state.ready} items={state.assets} itemRenderer={renderAsset} />
|
<Carousel ready={state.ready} items={state.assets} itemRenderer={renderAsset} />
|
||||||
<div class="message">{ state.message?.text }</div>
|
<div class="message">{ state.message?.text }</div>
|
||||||
|
<div class="options">
|
||||||
|
<Options />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TopicItemWrapper>
|
</TopicItemWrapper>
|
||||||
)
|
)
|
||||||
|
@ -15,6 +15,34 @@ export const TopicItemWrapper = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-left: 8px;
|
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 {
|
.info {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -14,6 +14,7 @@ export function useTopicItem(topic) {
|
|||||||
message: null,
|
message: null,
|
||||||
created: null,
|
created: null,
|
||||||
ready: false,
|
ready: false,
|
||||||
|
owner: false,
|
||||||
assets: [],
|
assets: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -26,6 +27,10 @@ export function useTopicItem(topic) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let owner = false;
|
||||||
|
if (profile.state.profile.guid == topic?.data?.topicDetail.guid) {
|
||||||
|
owner = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!topic?.data) {
|
if (!topic?.data) {
|
||||||
console.log("invalid topic:", topic);
|
console.log("invalid topic:", topic);
|
||||||
@ -56,11 +61,11 @@ export function useTopicItem(topic) {
|
|||||||
const { guid, created } = topic.data.topicDetail;
|
const { guid, created } = topic.data.topicDetail;
|
||||||
if (profile.state.profile.guid == guid) {
|
if (profile.state.profile.guid == guid) {
|
||||||
const { name, handle, imageUrl } = profile.actions.getProfile();
|
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 {
|
else {
|
||||||
const { name, handle, imageUrl } = card.actions.getCardProfileByGuid(guid);
|
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]);
|
}, [profile, card, conversation, topic]);
|
||||||
@ -68,6 +73,9 @@ export function useTopicItem(topic) {
|
|||||||
const actions = {
|
const actions = {
|
||||||
getAssetUrl: (assetId) => {
|
getAssetUrl: (assetId) => {
|
||||||
return conversation.actions.getAssetUrl(topic?.id, assetId);
|
return conversation.actions.getAssetUrl(topic?.id, assetId);
|
||||||
|
},
|
||||||
|
removeTopic: async () => {
|
||||||
|
return conversation.actions.removeTopic(topic.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ export function VirtualList({ id, items, itemRenderer }) {
|
|||||||
let containers = useRef([]);
|
let containers = useRef([]);
|
||||||
let listRef = useRef();
|
let listRef = useRef();
|
||||||
let key = useRef(null);
|
let key = useRef(null);
|
||||||
|
let itemView = useRef([]);
|
||||||
|
|
||||||
const addSlot = (id, slot) => {
|
const addSlot = (id, slot) => {
|
||||||
setSlots((m) => { m.set(id, slot); return new Map(m); })
|
setSlots((m) => { m.set(id, slot); return new Map(m); })
|
||||||
@ -59,6 +60,7 @@ export function VirtualList({ id, items, itemRenderer }) {
|
|||||||
containers.current = [];
|
containers.current = [];
|
||||||
clearSlots();
|
clearSlots();
|
||||||
}
|
}
|
||||||
|
itemView.current = items;
|
||||||
setItems();
|
setItems();
|
||||||
}, [items, id]);
|
}, [items, id]);
|
||||||
|
|
||||||
@ -87,7 +89,7 @@ export function VirtualList({ id, items, itemRenderer }) {
|
|||||||
|
|
||||||
const limitScroll = () => {
|
const limitScroll = () => {
|
||||||
let view = getPlacement();
|
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?.overscan?.bottom <= 0) {
|
||||||
if (view.position.height < viewHeight.current) {
|
if (view.position.height < viewHeight.current) {
|
||||||
if (scrollTop.current != view.position.top) {
|
if (scrollTop.current != view.position.top) {
|
||||||
@ -116,14 +118,14 @@ export function VirtualList({ id, items, itemRenderer }) {
|
|||||||
let view = getPlacement();
|
let view = getPlacement();
|
||||||
if (view) {
|
if (view) {
|
||||||
if (view.overscan.top < OVERSCAN) {
|
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 below = containers.current[0];
|
||||||
let container = {
|
let container = {
|
||||||
top: below.top - (DEFAULT_ITEM_HEIGHT + 2 * GUTTER),
|
top: below.top - (DEFAULT_ITEM_HEIGHT + 2 * GUTTER),
|
||||||
height: DEFAULT_ITEM_HEIGHT,
|
height: DEFAULT_ITEM_HEIGHT,
|
||||||
index: containers.current[0].index - 1,
|
index: containers.current[0].index - 1,
|
||||||
id: items[containers.current[0].index - 1].id,
|
id: itemView.current[containers.current[0].index - 1].id,
|
||||||
revision: items[containers.current[0].index - 1].revision,
|
revision: itemView.current[containers.current[0].index - 1].revision,
|
||||||
}
|
}
|
||||||
containers.current.unshift(container);
|
containers.current.unshift(container);
|
||||||
addSlot(container.id, getSlot(container))
|
addSlot(container.id, getSlot(container))
|
||||||
@ -131,14 +133,14 @@ export function VirtualList({ id, items, itemRenderer }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (view.overscan.bottom < OVERSCAN) {
|
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 above = containers.current[containers.current.length - 1];
|
||||||
let container = {
|
let container = {
|
||||||
top: above.top + above.height + 2 * GUTTER,
|
top: above.top + above.height + 2 * GUTTER,
|
||||||
height: DEFAULT_ITEM_HEIGHT,
|
height: DEFAULT_ITEM_HEIGHT,
|
||||||
index: containers.current[containers.current.length - 1].index + 1,
|
index: containers.current[containers.current.length - 1].index + 1,
|
||||||
id: items[containers.current[containers.current.length - 1].index + 1].id,
|
id: itemView.current[containers.current[containers.current.length - 1].index + 1].id,
|
||||||
revision: items[containers.current[containers.current.length - 1].index + 1].revision,
|
revision: itemView.current[containers.current[containers.current.length - 1].index + 1].revision,
|
||||||
}
|
}
|
||||||
containers.current.push(container);
|
containers.current.push(container);
|
||||||
addSlot(container.id, getSlot(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++) {
|
for (let i = 0; i < containers.current.length; i++) {
|
||||||
let container = containers.current[i];
|
let container = containers.current[i];
|
||||||
if (items.length <= container.index || items[container.index].id != container.id) {
|
if (itemView.current.length <= container.index || itemView.current[container.index].id != container.id) {
|
||||||
for (let j = i; j < containers.current.length; j++) {
|
while (containers.current.length > i) {
|
||||||
let popped = containers.current.pop();
|
let popped = containers.current.pop();
|
||||||
removeSlot(popped.id);
|
removeSlot(popped.id);
|
||||||
}
|
}
|
||||||
break;
|
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]));
|
updateSlot(container.id, getSlot(containers.current[i]));
|
||||||
containers.revision = items[container.index].revision;
|
containers.revision = itemView.current[container.index].revision;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// place first slot
|
// place first slot
|
||||||
if (items.length > 0 && canvasHeight > 0) {
|
if (itemView.current.length > 0 && canvasHeight > 0) {
|
||||||
let view = getPlacement();
|
let view = getPlacement();
|
||||||
if (!view) {
|
if (!view) {
|
||||||
let pos = canvasHeight / 2;
|
let pos = canvasHeight / 2;
|
||||||
@ -263,9 +265,9 @@ export function VirtualList({ id, items, itemRenderer }) {
|
|||||||
let container = {
|
let container = {
|
||||||
top: pos - DEFAULT_ITEM_HEIGHT,
|
top: pos - DEFAULT_ITEM_HEIGHT,
|
||||||
height: DEFAULT_ITEM_HEIGHT,
|
height: DEFAULT_ITEM_HEIGHT,
|
||||||
index: items.length - 1,
|
index: itemView.current.length - 1,
|
||||||
id: items[items.length - 1].id,
|
id: itemView.current[itemView.current.length - 1].id,
|
||||||
revision: items[items.length - 1].revision,
|
revision: itemView.current[itemView.current.length - 1].revision,
|
||||||
}
|
}
|
||||||
|
|
||||||
containers.current.push(container);
|
containers.current.push(container);
|
||||||
@ -289,7 +291,7 @@ export function VirtualList({ id, items, itemRenderer }) {
|
|||||||
if (typeof height !== 'undefined') {
|
if (typeof height !== 'undefined') {
|
||||||
onItemHeight(container, height);
|
onItemHeight(container, height);
|
||||||
}
|
}
|
||||||
return itemRenderer(items[container.index]);
|
return itemRenderer(itemView.current[container.index]);
|
||||||
}}
|
}}
|
||||||
</ReactResizeDetector>
|
</ReactResizeDetector>
|
||||||
</VirtualItem>
|
</VirtualItem>
|
||||||
|
8
net/web/src/api/removeChannelTopic.js
Normal file
8
net/web/src/api/removeChannelTopic.js
Normal file
@ -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);
|
||||||
|
}
|
8
net/web/src/api/removeContactChannelTopic.js
Normal file
8
net/web/src/api/removeContactChannelTopic.js
Normal file
@ -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);
|
||||||
|
}
|
@ -9,6 +9,7 @@ import { getCardImageUrl } from 'api/getCardImageUrl';
|
|||||||
import { getCardProfile } from 'api/getCardProfile';
|
import { getCardProfile } from 'api/getCardProfile';
|
||||||
import { getCardDetail } from 'api/getCardDetail';
|
import { getCardDetail } from 'api/getCardDetail';
|
||||||
import { removeContactChannel } from 'api/removeContactChannel';
|
import { removeContactChannel } from 'api/removeContactChannel';
|
||||||
|
import { removeContactChannelTopic } from 'api/removeContactChannelTopic';
|
||||||
import { addContactChannelTopic } from 'api/addContactChannelTopic';
|
import { addContactChannelTopic } from 'api/addContactChannelTopic';
|
||||||
import { setCardConnecting, setCardConnected, setCardConfirmed } from 'api/setCardStatus';
|
import { setCardConnecting, setCardConnected, setCardConfirmed } from 'api/setCardStatus';
|
||||||
import { getCardOpenMessage } from 'api/getCardOpenMessage';
|
import { getCardOpenMessage } from 'api/getCardOpenMessage';
|
||||||
@ -220,6 +221,12 @@ export function useCardContext() {
|
|||||||
let node = cardProfile.node;
|
let node = cardProfile.node;
|
||||||
await removeContactChannel(node, token, channelId);
|
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) => {
|
addChannelTopic: async (cardId, channelId, message, assets) => {
|
||||||
let { cardProfile, cardDetail } = cards.current.get(cardId).data;
|
let { cardProfile, cardDetail } = cards.current.get(cardId).data;
|
||||||
let token = cardProfile.guid + '.' + cardDetail.token;
|
let token = cardProfile.guid + '.' + cardDetail.token;
|
||||||
|
@ -4,6 +4,7 @@ import { getChannelDetail } from 'api/getChannelDetail';
|
|||||||
import { getChannelSummary } from 'api/getChannelSummary';
|
import { getChannelSummary } from 'api/getChannelSummary';
|
||||||
import { addChannel } from 'api/addChannel';
|
import { addChannel } from 'api/addChannel';
|
||||||
import { removeChannel } from 'api/removeChannel';
|
import { removeChannel } from 'api/removeChannel';
|
||||||
|
import { removeChannelTopic } from 'api/removeChannelTopic';
|
||||||
import { addChannelTopic } from 'api/addChannelTopic';
|
import { addChannelTopic } from 'api/addChannelTopic';
|
||||||
import { getChannelTopics } from 'api/getChannelTopics';
|
import { getChannelTopics } from 'api/getChannelTopics';
|
||||||
import { getChannelTopic } from 'api/getChannelTopic';
|
import { getChannelTopic } from 'api/getChannelTopic';
|
||||||
@ -109,6 +110,9 @@ export function useChannelContext() {
|
|||||||
removeChannel: async (channelId) => {
|
removeChannel: async (channelId) => {
|
||||||
return await removeChannel(access.current, channelId);
|
return await removeChannel(access.current, channelId);
|
||||||
},
|
},
|
||||||
|
removeChannelTopic: async (channelId, topicId) => {
|
||||||
|
return await removeChannelTopic(access.current, channelId, topicId);
|
||||||
|
},
|
||||||
addChannelTopic: async (channelId, message, assets) => {
|
addChannelTopic: async (channelId, message, assets) => {
|
||||||
await addChannelTopic(access.current, channelId, message, assets);
|
await addChannelTopic(access.current, channelId, message, assets);
|
||||||
},
|
},
|
||||||
|
@ -247,6 +247,15 @@ export function useConversationContext() {
|
|||||||
return await channel.actions.removeChannel(channelId);
|
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 }
|
return { state, actions }
|
||||||
|
Loading…
Reference in New Issue
Block a user