mirror of
https://github.com/balzack/databag.git
synced 2025-04-23 18:15:19 +00:00
added edit subject
This commit is contained in:
parent
01c8c2773c
commit
81a58c3622
@ -36,7 +36,7 @@ export function Conversation() {
|
||||
const attachAudio = useRef({ click: ()=>{} } as HTMLInputElement);
|
||||
const attachBinary = useRef({ click: ()=>{} } as HTMLInputElement);
|
||||
const { width, height, ref } = useResizeDetector();
|
||||
const input = useRef();
|
||||
const input = useRef(null as null | HTMLTextAreaElement);
|
||||
|
||||
const addImage = (image: File | undefined) => {
|
||||
if (image) {
|
||||
@ -118,8 +118,10 @@ export function Conversation() {
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
input.current.focus();
|
||||
useEffect(() => {
|
||||
if (input.current) {
|
||||
input.current.focus();
|
||||
}
|
||||
}, [sending]);
|
||||
|
||||
const topics = state.topics.map((topic, idx) => {
|
||||
|
@ -42,6 +42,17 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.editing {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
padding: 4px;
|
||||
justify-content: flex-end;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.thumbs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -2,14 +2,14 @@ import { useRef, useEffect, useState, useCallback } from 'react';
|
||||
import { avatar } from '../constants/Icons'
|
||||
import { Topic, Card, Profile } from 'databag-client-sdk';
|
||||
import classes from './Message.module.css'
|
||||
import { Image, Skeleton, ActionIcon } from '@mantine/core'
|
||||
import { Textarea, Button, Image, Skeleton, ActionIcon } from '@mantine/core'
|
||||
import { ImageAsset } from './imageAsset/ImageAsset';
|
||||
import { AudioAsset } from './audioAsset/AudioAsset';
|
||||
import { VideoAsset } from './videoAsset/VideoAsset';
|
||||
import { BinaryAsset } from './binaryAsset/BinaryAsset';
|
||||
import type { MediaAsset } from '../conversation/Conversation';
|
||||
import { useMessage } from './useMessage.hook';
|
||||
import { IconForbid, IconTrash, IconShare, IconEdit, IconAlertSquareRounded, IconChevronLeft, IconChevronRight, IconFileAlert } from '@tabler/icons-react';
|
||||
import { IconForbid, IconTrash, IconEdit, IconAlertSquareRounded, IconChevronLeft, IconChevronRight, IconFileAlert } from '@tabler/icons-react';
|
||||
import { useResizeDetector } from 'react-resize-detector';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
@ -19,11 +19,30 @@ export function Message({ topic, card, profile, host }: { topic: Topic, card: Ca
|
||||
const { locked, data, created, topicId, status, transform } = topic;
|
||||
const { name, handle, node } = profile || card || { name: null, handle: null, node: null }
|
||||
const { text, textColor, textSize, assets } = data || { text: null, textColor: null, textSize: null }
|
||||
const textStyle = textColor && textSize ? { color: textColor, fontSize: textSize } : textColor ? { color: textColor } : textSize ? { fontSize: textSize } : {}
|
||||
const textStyle = { color: textColor ? textColor : undefined, fontSize: textSize ? textSize : undefined };
|
||||
const logoUrl = profile ? profile.imageUrl : card ? card.imageUrl : avatar;
|
||||
const timestamp = actions.getTimestamp(created);
|
||||
const [message, setMessage] = useState(<p></p>);
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [editText, setEditText] = useState('');
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
const save = async () => {
|
||||
setSaving(true);
|
||||
try {
|
||||
await actions.saveSubject(topic.topicId, topic.sealed, {...topic.data, text: editText});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
setSaving(false);
|
||||
setEditing(false);
|
||||
}
|
||||
|
||||
const edit = () => {
|
||||
setEditing(true);
|
||||
setEditText(text);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const urlPattern = new RegExp('(https?:\\/\\/)?(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,4}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)');
|
||||
const hostPattern = new RegExp('^https?:\\/\\/', 'i');
|
||||
@ -105,9 +124,8 @@ export function Message({ topic, card, profile, host }: { topic: Topic, card: Ca
|
||||
</div>
|
||||
<div className={classes.options}>
|
||||
<div className={classes.surface}>
|
||||
<IconShare className={classes.option} />
|
||||
{ !locked && profile && (
|
||||
<IconEdit className={classes.option} />
|
||||
<IconEdit className={classes.option} onClick={edit} />
|
||||
)}
|
||||
{ (host || profile) && (
|
||||
<IconTrash className={classes.careful} />
|
||||
@ -117,13 +135,24 @@ export function Message({ topic, card, profile, host }: { topic: Topic, card: Ca
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{ !locked && status === 'confirmed' && text && (
|
||||
<div style={textStyle}>
|
||||
<div className={classes.padding}>
|
||||
<span className={classes.text}>{ message }</span>
|
||||
{! locked && status === 'confirmed' && editing && (
|
||||
<div className={classes.editing}>
|
||||
<Textarea styles={{ input: textStyle }} value={editText} onChange={(event) => setEditText(event.currentTarget.value)} placeholder={state.strings.newMessage} />
|
||||
<div className={classes.controls}>
|
||||
<Button variant="default" size="xs" onClick={() => setEditing(false)}>
|
||||
{state.strings.cancel}
|
||||
</Button>
|
||||
<Button variant="filled" size="xs" onClick={save} loading={saving}>
|
||||
{state.strings.save}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{ !locked && status === 'confirmed' && text && !editing && (
|
||||
<div className={classes.padding} style={textStyle}>
|
||||
<span className={classes.text}>{ message }</span>
|
||||
</div>
|
||||
)}
|
||||
{ !locked && status !== 'confirmed' && (
|
||||
<div className={classes.unconfirmed}>
|
||||
<Skeleton height={8} mt={6} radius="xl" />
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { useState, useContext, useEffect } from 'react'
|
||||
import { DisplayContext } from '../context/DisplayContext'
|
||||
import { AppContext } from '../context/AppContext';
|
||||
import { ContextType } from '../context/ContextType'
|
||||
|
||||
|
||||
export function useMessage() {
|
||||
const app = useContext(AppContext) as ContextType
|
||||
const display = useContext(DisplayContext) as ContextType
|
||||
const [state, setState] = useState({
|
||||
strings: display.state.strings,
|
||||
@ -22,6 +23,13 @@ export function useMessage() {
|
||||
}, [display.state]);
|
||||
|
||||
const actions = {
|
||||
saveSubject: async (topicId: string, sealed: boolean, subject: any) => {
|
||||
const focus = app.state.focus;
|
||||
if (focus) {
|
||||
console.log("SAVING", subject);
|
||||
await focus.setTopicSubject(topicId, sealed ? 'sealedtopic' : 'superbasictopic', ()=>subject, [], ()=>true);
|
||||
}
|
||||
},
|
||||
getTimestamp: (created: number) => {
|
||||
const now = Math.floor((new Date()).getTime() / 1000)
|
||||
const date = new Date(created * 1000);
|
||||
|
@ -694,68 +694,68 @@ export class FocusModule implements Focus {
|
||||
}
|
||||
}
|
||||
}
|
||||
const { text, textColor, textSize, assets } = subject(appAsset);
|
||||
}
|
||||
const { text, textColor, textSize, assets } = subject(appAsset);
|
||||
|
||||
// legacy support of 'superbasictopic' and 'sealedtopic'
|
||||
const getAsset = (assetId: string) => {
|
||||
const index = parseInt(assetId);
|
||||
const item = assetItems[index];
|
||||
if (!item) {
|
||||
throw new Error('invalid assetId in subject');
|
||||
}
|
||||
if (item.hosting === HostingMode.Inline) {
|
||||
return item.inline;
|
||||
} if (item.hosting === HostingMode.Split) {
|
||||
return item.split;
|
||||
} if (item.hosting === HostingMode.Basic) {
|
||||
return item.basic;
|
||||
} else {
|
||||
throw new Error('unknown hosting mode');
|
||||
}
|
||||
// legacy support of 'superbasictopic' and 'sealedtopic'
|
||||
const getAsset = (assetId: string) => {
|
||||
const index = parseInt(assetId);
|
||||
const item = assetItems[index];
|
||||
if (!item) {
|
||||
throw new Error('invalid assetId in subject');
|
||||
}
|
||||
const filtered = !assets ? [] : assets.filter((asset: any) => {
|
||||
if (sealed && asset.encrypted) {
|
||||
return true;
|
||||
} else if (!sealed && !asset.encrypted) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
const mapped = filtered.map((asset: any) => {
|
||||
if (sealed) {
|
||||
const { type, thumb, parts } = asset.encrypted;
|
||||
return { encrypted: { type, thumb: getAsset(thumb), parts: getAsset(parts) } };
|
||||
} else if (asset.image) {
|
||||
const { thumb, full } = asset.image;
|
||||
return { image: { thumb: getAsset(thumb), full: getAsset(full) } };
|
||||
} else if (asset.video) {
|
||||
const { thumb, lq, hd } = asset.video;
|
||||
return { video: { thumb: getAsset(thumb), lq: getAsset(lq), hd: getAsset(hd) } };
|
||||
} else if (asset.audio) {
|
||||
const { label, full } = asset.audio;
|
||||
return { audio: { label, full: getAsset(full) } };
|
||||
} else if (asset.binary) {
|
||||
const { label, extension, data } = asset.binary;
|
||||
return { binary: { label, extension, data: getAsset(data) } };
|
||||
}
|
||||
});
|
||||
const updated = { text, textColor, textSize, assets: mapped };
|
||||
// end of legacy support block
|
||||
|
||||
if (sealed) {
|
||||
if (!crypto || !channelKey) {
|
||||
throw new Error('encryption not set');
|
||||
}
|
||||
const subjectString = JSON.stringify({ message: updated });
|
||||
const { ivHex } = await crypto.aesIv();
|
||||
const { encryptedDataB64 } = await crypto.aesEncrypt(subjectString, ivHex, channelKey);
|
||||
const data = { messageEncrypted: encryptedDataB64, messageIv: ivHex };
|
||||
return await this.setRemoteChannelTopicSubject(topicId, type, data);
|
||||
if (item.hosting === HostingMode.Inline) {
|
||||
return item.inline;
|
||||
} if (item.hosting === HostingMode.Split) {
|
||||
return item.split;
|
||||
} if (item.hosting === HostingMode.Basic) {
|
||||
return item.basic;
|
||||
} else {
|
||||
return await this.setRemoteChannelTopicSubject(topicId, type, updated);
|
||||
throw new Error('unknown hosting mode');
|
||||
}
|
||||
}
|
||||
const filtered = !assets ? [] : assets.filter((asset: any) => {
|
||||
if (sealed && asset.encrypted) {
|
||||
return true;
|
||||
} else if (!sealed && !asset.encrypted) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
const mapped = filtered.map((asset: any) => {
|
||||
if (sealed) {
|
||||
const { type, thumb, parts } = asset.encrypted;
|
||||
return { encrypted: { type, thumb: getAsset(thumb), parts: getAsset(parts) } };
|
||||
} else if (asset.image) {
|
||||
const { thumb, full } = asset.image;
|
||||
return { image: { thumb: getAsset(thumb), full: getAsset(full) } };
|
||||
} else if (asset.video) {
|
||||
const { thumb, lq, hd } = asset.video;
|
||||
return { video: { thumb: getAsset(thumb), lq: getAsset(lq), hd: getAsset(hd) } };
|
||||
} else if (asset.audio) {
|
||||
const { label, full } = asset.audio;
|
||||
return { audio: { label, full: getAsset(full) } };
|
||||
} else if (asset.binary) {
|
||||
const { label, extension, data } = asset.binary;
|
||||
return { binary: { label, extension, data: getAsset(data) } };
|
||||
}
|
||||
});
|
||||
const updated = { text, textColor, textSize, assets: mapped };
|
||||
// end of legacy support block
|
||||
|
||||
if (sealed) {
|
||||
if (!crypto || !channelKey) {
|
||||
throw new Error('encryption not set');
|
||||
}
|
||||
const subjectString = JSON.stringify({ message: updated });
|
||||
const { ivHex } = await crypto.aesIv();
|
||||
const { encryptedDataB64 } = await crypto.aesEncrypt(subjectString, ivHex, channelKey);
|
||||
const data = { messageEncrypted: encryptedDataB64, messageIv: ivHex };
|
||||
return await this.setRemoteChannelTopicSubject(topicId, type, data);
|
||||
} else {
|
||||
return await this.setRemoteChannelTopicSubject(topicId, type, updated);
|
||||
}
|
||||
}
|
||||
|
||||
public async removeTopic(topicId: string) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user