mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-14 17:41:33 +03:00
publish: rewrite note components for graph-store
This commit is contained in:
parent
393f9fd53c
commit
2160bed3ca
@ -10,7 +10,6 @@ import ContactsApi from './contacts';
|
||||
import GroupsApi from './groups';
|
||||
import LaunchApi from './launch';
|
||||
import LinksApi from './links';
|
||||
import PublishApi from './publish';
|
||||
import GraphApi from './graph';
|
||||
import S3Api from './s3';
|
||||
|
||||
@ -23,7 +22,6 @@ export default class GlobalApi extends BaseApi<StoreState> {
|
||||
groups = new GroupsApi(this.ship, this.channel, this.store);
|
||||
launch = new LaunchApi(this.ship, this.channel, this.store);
|
||||
links = new LinksApi(this.ship, this.channel, this.store);
|
||||
publish = new PublishApi(this.ship, this.channel, this.store);
|
||||
s3 = new S3Api(this.ship, this.channel, this.store);
|
||||
graph = new GraphApi(this.ship, this.channel, this.store);
|
||||
|
||||
|
@ -9,6 +9,7 @@ import GlobalApi from "~/logic/api/global";
|
||||
import { Button, Box, Row, Text } from "@tlon/indigo-react";
|
||||
import styled from "styled-components";
|
||||
import { Author } from "./Author";
|
||||
import {GraphNode, TextContent} from "~/types/graph-update";
|
||||
|
||||
const ClickBox = styled(Box)`
|
||||
cursor: pointer;
|
||||
@ -17,48 +18,36 @@ const ClickBox = styled(Box)`
|
||||
|
||||
interface CommentItemProps {
|
||||
pending?: boolean;
|
||||
comment: Comment;
|
||||
comment: GraphNode;
|
||||
contacts: Contacts;
|
||||
book: string;
|
||||
ship: string;
|
||||
api: GlobalApi;
|
||||
note: NoteId;
|
||||
hideNicknames: boolean;
|
||||
hideAvatars: boolean;
|
||||
}
|
||||
|
||||
export function CommentItem(props: CommentItemProps) {
|
||||
const { ship, contacts, book, note, api } = props;
|
||||
const { ship, contacts, book, api } = props;
|
||||
const [editing, setEditing] = useState<boolean>(false);
|
||||
const commentPath = Object.keys(props.comment)[0];
|
||||
const commentData = props.comment[commentPath];
|
||||
const content = commentData.content.split("\n").map((line, i) => {
|
||||
const commentData = props.comment?.post;
|
||||
const comment = commentData.contents[0] as TextContent;
|
||||
const content = comment.text.split("\n").map((line, i) => {
|
||||
return (
|
||||
<Text className="mb2" key={i}>
|
||||
<Text mb="2" key={i}>
|
||||
{line}
|
||||
</Text>
|
||||
);
|
||||
});
|
||||
|
||||
const disabled = props.pending || window.ship !== commentData.author.slice(1);
|
||||
|
||||
const onUpdate = async ({ comment }) => {
|
||||
await api.publish.updateComment(
|
||||
ship.slice(1),
|
||||
book,
|
||||
note,
|
||||
commentPath,
|
||||
comment
|
||||
);
|
||||
setEditing(false);
|
||||
};
|
||||
const disabled = props.pending || window.ship !== commentData.author;
|
||||
|
||||
const onDelete = async () => {
|
||||
await api.publish.deleteComment(ship.slice(1), book, note, commentPath);
|
||||
await api.graph.removeNodes(ship, book, [commentData?.index]);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box mb={4} opacity={props.pending ? "60%" : "100%"}>
|
||||
<Box mb={4} opacity={commentData?.pending ? "60%" : "100%"}>
|
||||
<Row bg="white" my={3}>
|
||||
<Author
|
||||
showImage
|
||||
@ -68,32 +57,17 @@ export function CommentItem(props: CommentItemProps) {
|
||||
hideAvatars={props.hideAvatars}
|
||||
hideNicknames={props.hideNicknames}
|
||||
>
|
||||
{!disabled && !editing && (
|
||||
{!disabled && (
|
||||
<>
|
||||
<ClickBox color="green" onClick={() => setEditing(true)}>
|
||||
Edit
|
||||
</ClickBox>
|
||||
<ClickBox color="red" onClick={onDelete}>
|
||||
Delete
|
||||
</ClickBox>
|
||||
</>
|
||||
)}
|
||||
{editing && (
|
||||
<ClickBox onClick={() => setEditing(false)} color="red">
|
||||
Cancel
|
||||
</ClickBox>
|
||||
)}
|
||||
</Author>
|
||||
</Row>
|
||||
<Box mb={2}>
|
||||
{!editing && content}
|
||||
{editing && (
|
||||
<CommentInput
|
||||
onSubmit={onUpdate}
|
||||
initial={commentData.content}
|
||||
label="Update"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
@ -8,50 +8,34 @@ import { Contacts } from "~/types/contact-update";
|
||||
import _ from "lodash";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { FormikHelpers } from "formik";
|
||||
import {GraphNode, Graph} from "~/types/graph-update";
|
||||
import {createPost} from "~/logic/api/graph";
|
||||
|
||||
interface CommentsProps {
|
||||
comments: Comment[];
|
||||
comments: Graph;
|
||||
book: string;
|
||||
noteId: NoteId;
|
||||
note: Note;
|
||||
note: GraphNode;
|
||||
ship: string;
|
||||
contacts: Contacts;
|
||||
api: GlobalApi;
|
||||
numComments: number;
|
||||
enabled: boolean;
|
||||
hideAvatars: boolean;
|
||||
hideNicknames: boolean;
|
||||
}
|
||||
|
||||
export function Comments(props: CommentsProps) {
|
||||
const { comments, ship, book, note, api, noteId, numComments } = props;
|
||||
const [pending, setPending] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
_.forEach(comments, (comment: Comment) => {
|
||||
const { content } = comment[Object.keys(comment)[0]];
|
||||
setPending((p) => p.filter((p) => p !== content));
|
||||
});
|
||||
}, [numComments]);
|
||||
const { comments, ship, book, note, api } = props;
|
||||
|
||||
const onSubmit = async (
|
||||
{ comment },
|
||||
actions: FormikHelpers<{ comment: string }>
|
||||
) => {
|
||||
setPending((p) => [...p, comment]);
|
||||
const action = {
|
||||
"new-comment": {
|
||||
who: ship.slice(1),
|
||||
book: book,
|
||||
note: noteId,
|
||||
body: comment,
|
||||
},
|
||||
};
|
||||
try {
|
||||
await api.publish.publishAction(action);
|
||||
const post = createPost([{ text: comment }], note?.post?.index);
|
||||
await api.graph.addPost(ship, book, post)
|
||||
actions.resetForm();
|
||||
actions.setStatus({ success: null });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
actions.setStatus({ error: e.message });
|
||||
}
|
||||
};
|
||||
@ -59,37 +43,14 @@ export function Comments(props: CommentsProps) {
|
||||
return (
|
||||
<Col>
|
||||
<CommentInput onSubmit={onSubmit} />
|
||||
{Array.from(pending).map((com, i) => {
|
||||
const da = dateToDa(new Date());
|
||||
const ship = `~${window.ship}`;
|
||||
const comment = {
|
||||
[da]: {
|
||||
author: ship,
|
||||
content: com,
|
||||
"date-created": Math.round(new Date().getTime()),
|
||||
},
|
||||
} as Comment;
|
||||
return (
|
||||
<CommentItem
|
||||
comment={comment}
|
||||
key={i}
|
||||
contacts={props.contacts}
|
||||
ship={ship}
|
||||
pending={true}
|
||||
hideNicknames={props.hideNicknames}
|
||||
hideAvatars={props.hideAvatars}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{props.comments.map((com, i) => (
|
||||
{Array.from(comments).reverse().map(([idx, comment]) => (
|
||||
<CommentItem
|
||||
comment={com}
|
||||
key={i}
|
||||
comment={comment}
|
||||
key={idx}
|
||||
contacts={props.contacts}
|
||||
api={api}
|
||||
book={book}
|
||||
ship={ship}
|
||||
note={note["note-id"]}
|
||||
hideNicknames={props.hideNicknames}
|
||||
hideAvatars={props.hideAvatars}
|
||||
/>
|
||||
|
@ -1,22 +1,23 @@
|
||||
import React, { Component } from "react";
|
||||
import React from "react";
|
||||
import { PostFormSchema, PostForm } from "./NoteForm";
|
||||
import { Note } from "../../../../types/publish-update";
|
||||
import { FormikHelpers } from "formik";
|
||||
import GlobalApi from "../../../../api/global";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import { GraphNode, TextContent } from "~/types";
|
||||
interface EditPostProps {
|
||||
ship: string;
|
||||
noteId: string;
|
||||
note: Note;
|
||||
noteId: number;
|
||||
note: GraphNode;
|
||||
api: GlobalApi;
|
||||
book: string;
|
||||
}
|
||||
|
||||
export function EditPost(props: EditPostProps & RouteComponentProps) {
|
||||
const { note, book, noteId, api, ship, history } = props;
|
||||
const body = note.file.slice(note.file.indexOf(";>") + 2);
|
||||
const [title, file] = note.post.contents as TextContent[];
|
||||
const body = file.text.slice(file.text.indexOf(";>") + 2);
|
||||
const initial: PostFormSchema = {
|
||||
title: note.title,
|
||||
title: title.text,
|
||||
body,
|
||||
};
|
||||
|
||||
@ -25,11 +26,11 @@ export function EditPost(props: EditPostProps & RouteComponentProps) {
|
||||
actions: FormikHelpers<PostFormSchema>
|
||||
) => {
|
||||
const { title, body } = values;
|
||||
const host = ship.slice(1);
|
||||
try {
|
||||
await api.publish.editNote(host, book, noteId, title, body);
|
||||
history.push(`/~publish/notebook/${ship}/${book}/note/${noteId}`);
|
||||
// graph-store does not support editing nodes
|
||||
throw new Error("Unsupported");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
actions.setStatus({ error: "Failed to edit notebook" });
|
||||
}
|
||||
};
|
||||
@ -38,7 +39,7 @@ export function EditPost(props: EditPostProps & RouteComponentProps) {
|
||||
<PostForm
|
||||
initial={initial}
|
||||
onSubmit={onSubmit}
|
||||
submitLabel={`Update ${note.title}`}
|
||||
submitLabel={`Update ${title.text}`}
|
||||
loadingText="Updating..."
|
||||
/>
|
||||
);
|
||||
|
@ -5,21 +5,15 @@ import { Link, RouteComponentProps } from "react-router-dom";
|
||||
import { Spinner } from "~/views/components/Spinner";
|
||||
import { Comments } from "./Comments";
|
||||
import { NoteNavigation } from "./NoteNavigation";
|
||||
import {
|
||||
NoteId,
|
||||
Note as INote,
|
||||
Notebook,
|
||||
} from "~/types/publish-update";
|
||||
import { Contacts } from "~/types/contact-update";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { Author } from "./Author";
|
||||
import { Contacts, GraphNode, Graph} from "~/types";
|
||||
|
||||
interface NoteProps {
|
||||
ship: string;
|
||||
book: string;
|
||||
noteId: NoteId;
|
||||
note: INote;
|
||||
notebook: Notebook;
|
||||
note: GraphNode;
|
||||
notebook: Graph;
|
||||
contacts: Contacts;
|
||||
api: GlobalApi;
|
||||
hideAvatars: boolean;
|
||||
@ -28,31 +22,27 @@ interface NoteProps {
|
||||
|
||||
export function Note(props: NoteProps & RouteComponentProps) {
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
const { notebook, note, contacts, ship, book, noteId, api } = props;
|
||||
useEffect(() => {
|
||||
api.publish.fetchNote(ship, book, noteId);
|
||||
}, [ship, book, noteId]);
|
||||
const { contacts, ship, notebook, book, api, note } = props;
|
||||
|
||||
const baseUrl = `/~publish/notebook/${props.ship}/${props.book}`;
|
||||
const baseUrl = `/~publish/notebook/ship/${props.ship}/${props.book}`;
|
||||
|
||||
const deletePost = async () => {
|
||||
setDeleting(true);
|
||||
await api.publish.delNote(ship.slice(1), book, noteId);
|
||||
const indices = [note.post.index]
|
||||
await api.graph.removeNodes(ship, book, indices);
|
||||
props.history.push(baseUrl);
|
||||
};
|
||||
|
||||
const comments = note?.comments || [];
|
||||
const file = note?.file;
|
||||
const comments = note?.children
|
||||
const file = note?.post?.contents[1]?.text || "";
|
||||
const newfile = file ? file.slice(file.indexOf(";>") + 2) : "";
|
||||
|
||||
let editPost: JSX.Element | null = null;
|
||||
const editUrl = props.location.pathname + "/edit";
|
||||
if (`~${window.ship}` === note?.author) {
|
||||
editPost = (
|
||||
const noteId = parseInt(note.post.index.split('/')[1], 10);
|
||||
|
||||
let adminLinks: JSX.Element | null = null;
|
||||
if (window.ship === note?.post?.author) {
|
||||
adminLinks = (
|
||||
<Box display="inline-block">
|
||||
<Link to={editUrl}>
|
||||
<Text color="green">Edit</Text>
|
||||
</Link>
|
||||
<Text
|
||||
className="dib f9 red2 ml2 pointer"
|
||||
color="red"
|
||||
@ -81,16 +71,16 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
<Text>{"<- Notebook Index"}</Text>
|
||||
</Link>
|
||||
<Col>
|
||||
<Text display="block" mb={2}>{note?.title || ""}</Text>
|
||||
<Text display="block" mb={2}>{note?.post?.contents[0]?.text || ""}</Text>
|
||||
<Box display="flex">
|
||||
<Author
|
||||
hideNicknames={props.hideNicknames}
|
||||
hideAvatars={props.hideAvatars}
|
||||
ship={note?.author}
|
||||
ship={note?.post?.author}
|
||||
contacts={contacts}
|
||||
date={note?.["date-created"]}
|
||||
date={note?.post?.["time-sent"]}
|
||||
/>
|
||||
<Text ml={2}>{editPost}</Text>
|
||||
<Text ml={2}>{adminLinks}</Text>
|
||||
</Box>
|
||||
</Col>
|
||||
<Box color="black" className="md" style={{ overflowWrap: "break-word" }}>
|
||||
@ -98,25 +88,20 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
</Box>
|
||||
<NoteNavigation
|
||||
notebook={notebook}
|
||||
prevId={note?.["prev-note"] || undefined}
|
||||
nextId={note?.["next-note"] || undefined}
|
||||
noteId={noteId}
|
||||
ship={props.ship}
|
||||
book={props.book}
|
||||
/>
|
||||
{notebook.comments && (
|
||||
<Comments
|
||||
ship={ship}
|
||||
book={props.book}
|
||||
noteId={props.noteId}
|
||||
note={props.note}
|
||||
comments={comments}
|
||||
numComments={props.note["num-comments"]}
|
||||
contacts={props.contacts}
|
||||
api={props.api}
|
||||
hideNicknames={props.hideNicknames}
|
||||
hideAvatars={props.hideAvatars}
|
||||
/>
|
||||
)}
|
||||
<Comments
|
||||
ship={ship}
|
||||
book={props.book}
|
||||
note={props.note}
|
||||
comments={comments}
|
||||
contacts={props.contacts}
|
||||
api={props.api}
|
||||
hideNicknames={props.hideNicknames}
|
||||
hideAvatars={props.hideAvatars}
|
||||
/>
|
||||
<Spinner
|
||||
text="Deleting post..."
|
||||
awaiting={deleting}
|
||||
|
@ -2,7 +2,7 @@ import React, { Component } from "react";
|
||||
import moment from "moment";
|
||||
import { Box } from "@tlon/indigo-react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Notebook } from "../../../../types/publish-update";
|
||||
import {Graph, GraphNode} from "~/types";
|
||||
|
||||
function NavigationItem(props: {
|
||||
url: string;
|
||||
@ -10,7 +10,7 @@ function NavigationItem(props: {
|
||||
date: number;
|
||||
prev?: boolean;
|
||||
}) {
|
||||
const date = moment(date).fromNow();
|
||||
const date = moment(props.date).fromNow();
|
||||
return (
|
||||
<Box
|
||||
justifySelf={props.prev ? "start" : "end"}
|
||||
@ -30,12 +30,19 @@ function NavigationItem(props: {
|
||||
);
|
||||
}
|
||||
|
||||
function getAdjacentId(graph: Graph, child: number, backwards = false): number | null {
|
||||
const children = Array.from(graph);
|
||||
const i = children.findIndex(([index]) => index === child);
|
||||
const target = children[backwards ? i-1 : i+1];
|
||||
return target?.[0] || null;
|
||||
}
|
||||
|
||||
|
||||
interface NoteNavigationProps {
|
||||
book: string;
|
||||
nextId?: string;
|
||||
prevId?: string;
|
||||
noteId: number;
|
||||
ship: string;
|
||||
notebook: Notebook;
|
||||
notebook: Graph;
|
||||
}
|
||||
|
||||
export function NoteNavigation(props: NoteNavigationProps) {
|
||||
@ -43,32 +50,39 @@ export function NoteNavigation(props: NoteNavigationProps) {
|
||||
let prevComponent = <Box />;
|
||||
let nextUrl = "";
|
||||
let prevUrl = "";
|
||||
const { nextId, prevId, notebook } = props;
|
||||
const next =
|
||||
nextId && nextId in notebook?.notes ? notebook?.notes[nextId] : null;
|
||||
const { noteId, notebook } = props;
|
||||
if(!notebook) {
|
||||
return null;
|
||||
}
|
||||
const nextId = getAdjacentId(notebook, noteId);
|
||||
const prevId = getAdjacentId(notebook, noteId, true);
|
||||
const next = nextId && notebook.get(nextId);
|
||||
const prev = prevId && notebook.get(prevId);
|
||||
|
||||
const prev =
|
||||
prevId && prevId in notebook?.notes ? notebook?.notes[prevId] : null;
|
||||
if (!next && !prev) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (next) {
|
||||
nextUrl = `/~publish/notebook/${props.ship}/${props.book}/note/${props.nextId}`;
|
||||
nextUrl = `/~publish/notebook/ship/${props.ship}/${props.book}/note/${nextId}`;
|
||||
const title = next.post.contents[0]?.text;
|
||||
const date = next.post['time-sent'];
|
||||
nextComponent = (
|
||||
<NavigationItem
|
||||
title={next.title}
|
||||
date={next["date-created"]}
|
||||
title={title}
|
||||
date={date}
|
||||
url={nextUrl}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (prev) {
|
||||
prevUrl = `/~publish/notebook/${props.ship}/${props.book}/note/${props.prevId}`;
|
||||
prevUrl = `/~publish/notebook/ship/${props.ship}/${props.book}/note/${prevId}`;
|
||||
const title = prev.post.contents[0]?.text;
|
||||
const date = prev.post['time-sent'];
|
||||
prevComponent = (
|
||||
<NavigationItem
|
||||
title={prev.title}
|
||||
date={prev["date-created"]}
|
||||
title={title}
|
||||
date={date}
|
||||
url={prevUrl}
|
||||
prev
|
||||
/>
|
||||
|
@ -1,27 +1,29 @@
|
||||
import React from "react";
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
|
||||
import { NoteId, Note as INote, Notebook } from "~/types/publish-update";
|
||||
import { Contacts } from "~/types/contact-update";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import Note from "./Note";
|
||||
import { EditPost } from "./EditPost";
|
||||
|
||||
import { GraphNode, Graph, Contacts } from "~/types";
|
||||
|
||||
interface NoteRoutesProps {
|
||||
ship: string;
|
||||
book: string;
|
||||
noteId: NoteId;
|
||||
note: INote;
|
||||
notebook: Notebook;
|
||||
note: GraphNode;
|
||||
noteId: number;
|
||||
notebook: Graph;
|
||||
contacts: Contacts;
|
||||
api: GlobalApi;
|
||||
hideAvatars: boolean;
|
||||
hideNicknames: boolean;
|
||||
}
|
||||
|
||||
export function NoteRoutes(props: NoteRoutesProps & RouteComponentProps) {
|
||||
const { ship, book, noteId } = props;
|
||||
|
||||
const baseUrl = `/~publish/notebook/${ship}/${book}/note/${noteId}`;
|
||||
const baseUrl = `/~publish/notebook/ship/${ship}/${book}/note/${noteId}`;
|
||||
|
||||
const relativePath = (path: string) => `${baseUrl}${path}`;
|
||||
return (
|
||||
|
Loading…
Reference in New Issue
Block a user