publish: update FE for versioned posts

This commit is contained in:
Liam Fitzgerald 2020-09-15 15:53:20 +10:00
parent a81c41b942
commit 80f5747cd5
7 changed files with 181 additions and 39 deletions

View File

@ -0,0 +1,117 @@
import { Post, GraphNode, TextContent, Graph, NodeMap } from "~/types";
const buntPost = (): Post => ({
author: '',
contents: [],
hash: null,
index: '',
signatures: [],
'time-sent': 0
});
export function makeNodeMap(posts: Post[]): Record<string, GraphNode> {
let nodes = {};
posts.forEach((p) => {
nodes[p.index] = { children: { empty: null }, post: p };
});
return nodes;
}
export function newPost(
title: string,
body: string
): [number, NodeMap] {
const now = Date.now();
const root: Post = {
author: `~${window.ship}`,
index: "/" + now,
"time-sent": now,
contents: [],
hash: null,
signatures: [],
};
const revContainer: Post = { ...root, index: root.index + "/1" };
const commentsContainer = { ...root, index: root.index + "/2" };
const firstRevision: Post = {
...revContainer,
index: revContainer.index + "/1",
contents: [{ text: title }, { text: body }],
};
const nodes = {
[root.index]: {
post: root,
children: {
graph: {
1: {
post: revContainer,
children: {
graph: {
1: {
post: firstRevision,
children: { empty: null },
},
},
},
},
2: {
post: commentsContainer,
children: { empty: null },
},
},
},
},
};
return [now, nodes];
}
export function editPost(rev: number, noteId: number, title: string, body: string) {
const now = Date.now();
const newRev: Post = {
author: `~${window.ship}`,
index: `/${noteId}/1/${rev}`,
"time-sent": now,
contents: [{ text: title }, { text: body }],
hash: null,
signatures: [],
};
const nodes = {
[newRev.index]: {
post: newRev,
children: { empty: null }
}
};
return nodes;
}
export function getLatestRevision(node: GraphNode): [number, string, string, Post] {
const revs = node.children.get(1);
const empty = [1, "", "", buntPost()] as [number, string, string, Post];
if(!revs) {
return empty;
}
const [revNum, rev] = [...revs.children][0];
if(!rev) {
return empty
}
const [title, body] = rev.post.contents as TextContent[];
return [revNum, title.text, body.text, rev.post];
}
export function getComments(node: GraphNode): GraphNode {
const comments = node.children.get(2);
if(!comments) {
return { post: buntPost(), children: new Map() }
}
return comments;
}
export function getSnippet(body: string) {
const start = body.slice(0, 400);
return start === body ? start : `${start}...`;
}

View File

@ -13,7 +13,7 @@ import {createPost} from "~/logic/api/graph";
import { LocalUpdateRemoteContentPolicy } from "~/types";
interface CommentsProps {
comments: Graph;
comments: GraphNode;
book: string;
note: GraphNode;
ship: string;
@ -32,7 +32,7 @@ export function Comments(props: CommentsProps) {
actions: FormikHelpers<{ comment: string }>
) => {
try {
const post = createPost([{ text: comment }], note?.post?.index);
const post = createPost([{ text: comment }], comments?.post?.index);
await api.graph.addPost(ship, book, post)
actions.resetForm();
actions.setStatus({ success: null });
@ -45,7 +45,7 @@ export function Comments(props: CommentsProps) {
return (
<Col>
<CommentInput onSubmit={onSubmit} />
{Array.from(comments).reverse().map(([idx, comment]) => (
{Array.from(comments.children).reverse().map(([idx, comment]) => (
<CommentItem
comment={comment}
key={idx}

View File

@ -4,6 +4,8 @@ import { FormikHelpers } from "formik";
import GlobalApi from "~/logic/api/global";
import { RouteComponentProps } from "react-router-dom";
import { GraphNode, TextContent } from "~/types";
import { getLatestRevision, editPost } from "~/logic/lib/publish";
import {useWaitForProps} from "~/logic/lib/useWaitForProps";
interface EditPostProps {
ship: string;
noteId: number;
@ -14,10 +16,11 @@ interface EditPostProps {
export function EditPost(props: EditPostProps & RouteComponentProps) {
const { note, book, noteId, api, ship, history } = props;
const [title, file] = note.post.contents as TextContent[];
const body = file.text.slice(file.text.indexOf(";>") + 2);
const [revNum, title, body] = getLatestRevision(note);
const waiter = useWaitForProps(props);
const initial: PostFormSchema = {
title: title.text,
title,
body,
};
@ -27,8 +30,14 @@ export function EditPost(props: EditPostProps & RouteComponentProps) {
) => {
const { title, body } = values;
try {
// graph-store does not support editing nodes
throw new Error("Unsupported");
const newRev = revNum + 1;
const nodes = editPost(newRev, noteId, title, body);
await api.graph.addNodes(ship, book, nodes);
await waiter(p => {
const [rev] = getLatestRevision(note);
return rev === newRev;
});
history.push(`/~publish/notebook/ship/${ship}/${book}/note/${noteId}`);
} catch (e) {
console.error(e);
actions.setStatus({ error: "Failed to edit notebook" });
@ -39,7 +48,7 @@ export function EditPost(props: EditPostProps & RouteComponentProps) {
<PostForm
initial={initial}
onSubmit={onSubmit}
submitLabel={`Update ${title.text}`}
submitLabel={`Update ${title}`}
loadingText="Updating..."
/>
);

View File

@ -6,6 +6,7 @@ import { Spinner } from "~/views/components/Spinner";
import { Comments } from "./Comments";
import { NoteNavigation } from "./NoteNavigation";
import GlobalApi from "~/logic/api/global";
import { getLatestRevision, getComments } from '~/logic/lib/publish';
import { Author } from "./Author";
import { Contacts, GraphNode, Graph, LocalUpdateRemoteContentPolicy } from "~/types";
@ -34,18 +35,24 @@ export function Note(props: NoteProps & RouteComponentProps) {
props.history.push(baseUrl);
};
const comments = note?.children
const file = note?.post?.contents[1]?.text || "";
const newfile = file ? file.slice(file.indexOf(";>") + 2) : "";
const comments = getComments(note);
const [revNum, title, body, post] = getLatestRevision(note);
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={`${baseUrl}/note/${noteId}/edit`}>
<Text
color="green"
ml={2}
>
Update
</Text>
</Link>
<Text
className="dib f9 red2 ml2 pointer"
color="red"
ml={2}
onClick={deletePost}
@ -72,20 +79,20 @@ export function Note(props: NoteProps & RouteComponentProps) {
<Text>{"<- Notebook Index"}</Text>
</Link>
<Col>
<Text display="block" mb={2}>{note?.post?.contents[0]?.text || ""}</Text>
<Text display="block" mb={2}>{title || ""}</Text>
<Box display="flex">
<Author
hideNicknames={props.hideNicknames}
hideAvatars={props.hideAvatars}
ship={note?.post?.author}
ship={post?.author}
contacts={contacts}
date={note?.post?.["time-sent"]}
date={post?.["time-sent"]}
/>
<Text ml={2}>{adminLinks}</Text>
</Box>
</Col>
<Box color="black" className="md" style={{ overflowWrap: "break-word" }}>
<ReactMarkdown source={newfile} linkTarget={"_blank"} />
<ReactMarkdown source={body} linkTarget={"_blank"} />
</Box>
<NoteNavigation
notebook={notebook}

View File

@ -3,6 +3,7 @@ import moment from "moment";
import { Box } from "@tlon/indigo-react";
import { Link } from "react-router-dom";
import {Graph, GraphNode} from "~/types";
import {getLatestRevision} from "~/logic/lib/publish";
function NavigationItem(props: {
url: string;
@ -36,6 +37,10 @@ function getAdjacentId(graph: Graph, child: number, backwards = false): number |
const target = children[backwards ? i-1 : i+1];
return target?.[0] || null;
}
function makeNoteUrl(ship: string, book: string, noteId: number) {
return `/~publish/notebook/ship/${ship}/${book}/note/${noteId}`;
}
interface NoteNavigationProps {
@ -48,8 +53,6 @@ interface NoteNavigationProps {
export function NoteNavigation(props: NoteNavigationProps) {
let nextComponent = <Box />;
let prevComponent = <Box />;
let nextUrl = "";
let prevUrl = "";
const { noteId, notebook } = props;
if(!notebook) {
return null;
@ -63,10 +66,10 @@ export function NoteNavigation(props: NoteNavigationProps) {
return null;
}
if (next) {
nextUrl = `/~publish/notebook/ship/${props.ship}/${props.book}/note/${nextId}`;
const title = next.post.contents[0]?.text;
const date = next.post['time-sent'];
if (next && nextId) {
const nextUrl = makeNoteUrl(props.ship, props.book, nextId);
const [,title,, post] = getLatestRevision(next);
const date = post['time-sent'];
nextComponent = (
<NavigationItem
title={title}
@ -75,10 +78,10 @@ export function NoteNavigation(props: NoteNavigationProps) {
/>
);
}
if (prev) {
prevUrl = `/~publish/notebook/ship/${props.ship}/${props.book}/note/${prevId}`;
const title = prev.post.contents[0]?.text;
const date = prev.post['time-sent'];
if (prev && prevId) {
const prevUrl = makeNoteUrl(props.ship, props.book, prevId);
const [,title,, post] = getLatestRevision(prev);
const date = post['time-sent'];
prevComponent = (
<NavigationItem
title={title}

View File

@ -8,6 +8,11 @@ import moment from "moment";
import { Link } from "react-router-dom";
import styled from "styled-components";
import { GraphNode } from "~/types/graph-update";
import {
getComments,
getLatestRevision,
getSnippet,
} from "~/logic/lib/publish";
interface NotePreviewProps {
host: string;
@ -36,7 +41,7 @@ export function NotePreview(props: NotePreviewProps) {
name = cite(post?.author);
}
const numComments = node.children.size;
const numComments = getComments(node).children.size;
const commentDesc =
numComments === 0
? "No Comments"
@ -51,15 +56,19 @@ export function NotePreview(props: NotePreviewProps) {
// stubbing pending notification-store
const isRead = true;
const [rev, title, body] = getLatestRevision(node);
const snippet = getSnippet(body);
return (
<Link to={url}>
<Col mb={4}>
<WrappedBox mb={1}>{post.contents[0]?.text}</WrappedBox>
<WrappedBox mb={1}>{title}</WrappedBox>
<WrappedBox mb={1}>
<ReactMarkdown
unwrapDisallowed
allowedTypes={["text", "root", "break", "paragraph"]}
source={post.contents[1]?.text}
source={snippet}
/>
</WrappedBox>
<Box color="gray" display="flex">
@ -74,7 +83,8 @@ export function NotePreview(props: NotePreviewProps) {
<Box color={isRead ? "gray" : "green"} mr={3}>
{date}
</Box>
<Box>{commentDesc}</Box>
<Box mr={3}>{commentDesc}</Box>
<Box>{rev === 1 ? `1 Revision` : `${rev} Revisions`}</Box>
</Box>
</Col>
</Link>

View File

@ -7,6 +7,7 @@ import { PostForm, PostFormSchema } from "./NoteForm";
import {createPost} from "~/logic/api/graph";
import {Graph} from "~/types/graph-update";
import {Association} from "~/types";
import {newPost} from "~/logic/lib/publish";
interface NewPostProps {
api: GlobalApi;
@ -27,13 +28,8 @@ export default function NewPost(props: NewPostProps & RouteComponentProps) {
) => {
const { title, body } = values;
try {
const post = createPost([{ text: title }, { text: body }])
const noteId = parseInt(post.index.split('/')[1], 10);
await api.graph.addPost(ship, book, post)
await waiter(p => {
const { graph } = p;
return graph.has(noteId);
});
const [noteId, nodes] = newPost(title, body)
await api.graph.addNodes(ship, book, nodes)
history.push(`/~publish/notebook/ship/${ship}/${book}/note/${noteId}`);
} catch (e) {
console.error(e);