publish: rewrite notebook components for graph-store

This commit is contained in:
Liam Fitzgerald 2020-09-10 20:10:07 +10:00
parent 22b6277abe
commit 393f9fd53c
7 changed files with 123 additions and 104 deletions

View File

@ -7,11 +7,12 @@ import ReactMarkdown from "react-markdown";
import moment from "moment";
import { Link } from "react-router-dom";
import styled from "styled-components";
import { GraphNode } from "~/types/graph-update";
interface NotePreviewProps {
host: string;
book: string;
note: Note;
node: GraphNode;
contact?: Contact;
hideNicknames?: boolean;
}
@ -21,47 +22,60 @@ const WrappedBox = styled(Box)`
`;
export function NotePreview(props: NotePreviewProps) {
const { note, contact } = props;
const { node, contact } = props;
const { post } = node;
if (!post) {
return null;
}
let name = note.author;
let name = post?.author;
if (contact && !props.hideNicknames) {
name = contact.nickname.length > 0 ? contact.nickname : note.author;
name = contact.nickname.length > 0 ? contact.nickname : post?.author;
}
if (name === note.author) {
name = cite(note.author);
if (name === post?.author) {
name = cite(post?.author);
}
let comment = "No Comments";
if (note["num-comments"] == 1) {
comment = "1 Comment";
} else if (note["num-comments"] > 1) {
comment = `${note["num-comments"]} Comments`;
}
const date = moment(note["date-created"]).fromNow();
const numComments = node.children.size;
const commentDesc =
numComments === 0
? "No Comments"
: numComments === 1
? "1 Comment"
: `${numComments} Comments`;
const date = moment(post["time-sent"]).fromNow();
//const popout = props.popout ? "popout/" : "";
const url = `/~publish/notebook/${props.host}/${props.book}/note/${note["note-id"]}`;
const url = `/~publish/notebook/ship/${props.host}/${props.book}/note/${
post.index.split("/")[1]
}`;
// stubbing pending notification-store
const isRead = true;
return (
<Link to={url}>
<Col mb={4}>
<WrappedBox mb={1}>{note.title}</WrappedBox>
<WrappedBox mb={1}>{post.contents[0]?.text}</WrappedBox>
<WrappedBox mb={1}>
<ReactMarkdown
unwrapDisallowed
allowedTypes={["text", "root", "break", "paragraph"]}
source={note.snippet}
source={post.contents[1]?.text}
/>
</WrappedBox>
<Box color="gray" display="flex">
<Box
mr={3}
fontFamily={contact?.nickname && !props.hideNicknames ? "sans" : "mono"}
fontFamily={
contact?.nickname && !props.hideNicknames ? "sans" : "mono"
}
>
{name}
</Box>
<Box color={note.read ? "gray" : "green"} mr={3}>
<Box color={isRead ? "gray" : "green"} mr={3}>
{date}
</Box>
<Box>{comment}</Box>
<Box>{commentDesc}</Box>
</Box>
</Col>
</Link>

View File

@ -20,6 +20,8 @@ import { Groups } from "~/types/group-update";
import { Contacts, Rolodex } from "~/types/contact-update";
import GlobalApi from "~/logic/api/global";
import styled from "styled-components";
import {Association, Associations} from "~/types";
import {Graph} from "~/types/graph-update";
const TabList = styled(_TabList)`
margin-bottom: ${(p) => p.theme.space[4]}px;
@ -33,18 +35,20 @@ interface NotebookProps {
api: GlobalApi;
ship: string;
book: string;
notebook: INotebook;
graph: Graph;
notebookContacts: Contacts;
association: Association;
associations: Associations;
contacts: Rolodex;
groups: Groups;
hideNicknames: boolean;
}
export function Notebook(props: NotebookProps & RouteComponentProps) {
const { api, ship, book, notebook, notebookContacts, groups } = props;
const { api, ship, book, association, notebookContacts, groups } = props;
const contact = notebookContacts[ship];
const group = groups[notebook?.["writers-group-path"]];
const group = groups[association['group-path']];
const role = group ? roleForShip(group, window.ship) : undefined;
const isOwn = `~${window.ship}` === ship;
const isAdmin = role === "admin" || isOwn;
@ -52,10 +56,10 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
const isWriter =
isOwn || group.tags?.publish?.[`writers-${book}`]?.has(window.ship);
const notesList = notebook?.["notes-by-date"] || [];
const notes = notebook?.notes || {};
const showNickname = contact?.nickname && !props.hideNicknames;
const { metadata } = props.association || {};
return (
<Box
pt={4}
@ -71,7 +75,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
<Link to="/~publish">{"<- All Notebooks"}</Link>
</Box>
<Box>
<Text> {notebook?.title}</Text>
<Text> {metadata?.title}</Text>
<br />
<Text color="lightGray">by </Text>
<Text fontFamily={showNickname ? "sans" : "mono"}>
@ -80,7 +84,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
</Box>
<Row justifyContent={["flex-start", "flex-end"]}>
{isWriter && (
<Link to={`/~publish/notebook/${ship}/${book}/new`}>
<Link to={`/~publish/notebook/ship/${ship}/${book}/new`}>
<Button primary border>
New Post
</Button>
@ -103,8 +107,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
<TabPanels>
<TabPanel>
<NotebookPosts
notes={notes}
list={notesList}
graph={props.graph}
host={ship}
book={book}
contacts={notebookContacts}
@ -112,15 +115,16 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
/>
</TabPanel>
<TabPanel>
<Box color="black">{notebook?.about}</Box>
<Box color="black">{metadata?.description}</Box>
</TabPanel>
<TabPanel>
<Subscribers
host={ship}
book={book}
notebook={notebook}
association={association}
associations={props.associations}
api={api}
groups={groups}
contacts={props.contacts}
/>
</TabPanel>
<TabPanel>
@ -128,7 +132,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
host={ship}
book={book}
api={api}
notebook={notebook}
association={props.association}
contacts={notebookContacts}
/>
</TabPanel>

View File

@ -29,7 +29,7 @@ function UnreadCount(props: { unread: number }) {
export function NotebookItem(props: NotebookItemProps) {
return (
<Link to={"/~publish/notebook/" + props.path}>
<Link to={"/~publish/notebook" + props.path}>
<HoverBox
bg="white"
bgActive="washedGray"

View File

@ -1,13 +1,11 @@
import React, { Component } from "react";
import { Col } from "@tlon/indigo-react";
import { Notes, NoteId } from "../../../../types/publish-update";
import { NotePreview } from "./NotePreview";
import { Contacts } from "../../../../types/contact-update";
import { Contacts, Graph } from "~/types";
interface NotebookPostsProps {
list: NoteId[];
contacts: Contacts;
notes: Notes;
graph: Graph;
host: string;
book: string;
hideNicknames?: boolean;
@ -16,23 +14,19 @@ interface NotebookPostsProps {
export function NotebookPosts(props: NotebookPostsProps) {
return (
<Col>
{props.list.map((noteId: NoteId) => {
const note = props.notes[noteId];
if (!note) {
console.log(noteId);
return null;
}
return (
<NotePreview
key={noteId}
host={props.host}
book={props.book}
note={note}
contact={props.contacts[note.author.substr(1)]}
hideNicknames={props.hideNicknames}
/>
);
})}
{Array.from(props.graph || []).map(
([date, node]) =>
node && (
<NotePreview
key={date}
host={props.host}
book={props.book}
contact={props.contacts[node.post.author]}
node={node}
hideNicknames={props.hideNicknames}
/>
)
)}
</Col>
);
}

View File

@ -1,40 +1,37 @@
import React, { useEffect } from "react";
import { RouteComponentProps, Link, Route, Switch } from "react-router-dom";
import { Box, Text } from "@tlon/indigo-react";
import GlobalApi from "../../../../api/global";
import { PublishContent } from "./PublishContent";
import { Notebook as INotebook } from "../../../../types/publish-update";
import { Groups } from "../../../../types/group-update";
import { Contacts, Rolodex } from "../../../../types/contact-update";
import { RouteComponentProps, Route, Switch } from "react-router-dom";
import GlobalApi from "~/logic/api/global";
import Notebook from "./Notebook";
import NewPost from "./new-post";
import { NoteRoutes } from './NoteRoutes';
import { Association, Associations, Graphs, Groups, Contacts, Rolodex } from "~/types";
interface NotebookRoutesProps {
api: GlobalApi;
ship: string;
book: string;
notes: any;
notebook: INotebook;
graphs: Graphs;
notebookContacts: Contacts;
contacts: Rolodex;
groups: Groups;
hideAvatars: boolean;
hideNicknames: boolean;
association: Association;
associations: Associations;
}
export function NotebookRoutes(
props: NotebookRoutesProps & RouteComponentProps
) {
const { ship, book, api, notebook, notebookContacts } = props;
const { ship, book, api, notebookContacts } = props;
useEffect(() => {
api.publish.fetchNotesPage(ship, book, 1, 50);
api.publish.fetchNotebook(ship, book);
ship && book && api.graph.getGraph(ship, book);
}, [ship, book]);
const graph = props.graphs[`${ship.slice(1)}/${book}`];
const baseUrl = `/~publish/notebook/${ship}/${book}`;
const baseUrl = `/~publish/notebook/ship/${ship}/${book}`;
const relativePath = (path: string) => `${baseUrl}${path}`;
return (
@ -43,7 +40,7 @@ export function NotebookRoutes(
path={baseUrl}
exact
render={(routeProps) => {
return <Notebook {...props} />;
return <Notebook {...props} graph={graph} />;
}}
/>
<Route
@ -54,7 +51,8 @@ export function NotebookRoutes(
api={api}
book={book}
ship={ship}
notebook={notebook}
association={props.association}
graph={graph}
/>
)}
/>
@ -62,15 +60,23 @@ export function NotebookRoutes(
path={relativePath("/note/:noteId")}
render={(routeProps) => {
const { noteId } = routeProps.match.params;
const note = notebook?.notes[noteId];
const noteIdNum = parseInt(noteId, 10);
if(!graph) {
return null;
}
const note = graph.get(noteIdNum);
if(!note) {
return null;
}
return (
<NoteRoutes
api={api}
book={book}
ship={ship}
noteId={noteId}
notebook={notebook}
note={note}
notebook={graph}
noteId={noteIdNum}
contacts={notebookContacts}
hideAvatars={props.hideAvatars}
hideNicknames={props.hideNicknames}

View File

@ -4,24 +4,23 @@ import * as Yup from "yup";
import {
Box,
Input,
Checkbox,
Col,
InputLabel,
InputCaption,
Button,
Center,
} from "@tlon/indigo-react";
import { Formik, Form, useFormikContext, FormikHelpers } from "formik";
import GlobalApi from "~/logic/api/global";
import { Notebook } from "~/types/publish-update";
import { Contacts } from "~/types/contact-update";
import { FormError } from "~/views/components/FormError";
import { RouteComponentProps, useHistory } from "react-router-dom";
import { useHistory } from "react-router-dom";
import { Association } from "~/types";
import { uxToHex } from "~/logic/lib/util";
interface SettingsProps {
host: string;
book: string;
notebook: Notebook;
association: Association;
contacts: Contacts;
api: GlobalApi;
}
@ -29,13 +28,11 @@ interface SettingsProps {
interface FormSchema {
name: string;
description: string;
comments: boolean;
}
const formSchema = Yup.object({
name: Yup.string().required("Notebook must have a name"),
description: Yup.string(),
comments: Yup.boolean(),
});
const ResetOnPropsChange = (props: { init: FormSchema; book: string }) => {
@ -48,12 +45,12 @@ const ResetOnPropsChange = (props: { init: FormSchema; book: string }) => {
};
export function Settings(props: SettingsProps) {
const { host, notebook, api, book } = props;
const { api, book } = props;
const history = useHistory();
const { metadata } = props.association || {};
const initialValues: FormSchema = {
name: notebook?.title,
description: notebook?.about,
comments: notebook?.comments,
name: metadata?.title,
description: metadata?.description,
};
const onSubmit = async (
@ -61,9 +58,16 @@ export function Settings(props: SettingsProps) {
actions: FormikHelpers<FormSchema>
) => {
try {
const { name, description, comments } = values;
await api.publish.editBook(book, name, description, comments);
api.publish.fetchNotebook(host, book);
const { name, description } = values;
await api.metadata.metadataAdd(
"publish",
props.association["app-path"],
props.association["group-path"],
name,
description,
props.association.metadata["date-created"],
uxToHex(props.association.metadata.color)
);
actions.setStatus({ success: null });
} catch (e) {
console.log(e);
@ -72,7 +76,7 @@ export function Settings(props: SettingsProps) {
};
const onDelete = async () => {
await api.publish.delBook(book);
await api.graph.deleteGraph(book);
history.push("/~publish");
};
@ -96,7 +100,7 @@ export function Settings(props: SettingsProps) {
Permanently delete this notebook. (All current members will no
longer see this notebook.)
</InputCaption>
<Button onClick={onDelete} mt={1} border error>
<Button type="button" onClick={onDelete} mt={1} border error>
Delete this notebook
</Button>
</Col>
@ -110,11 +114,6 @@ export function Settings(props: SettingsProps) {
label="Change description"
caption="Change the description of this notebook"
/>
<Checkbox
id="comments"
label="Comments"
caption="Subscribers may comment when enabled"
/>
<ResetOnPropsChange init={initialValues} book={book} />
<AsyncButton loadingText="Updating.." border>
Save

View File

@ -4,22 +4,22 @@ import { resourceFromPath } from '~/logic/lib/group';
import {Notebook} from '~/types/publish-update';
import GlobalApi from '~/logic/api/global';
import {Groups} from '~/types/group-update';
import {Associations} from '~/types/metadata-update';
import {Associations, Association} from '~/types/metadata-update';
import {Rolodex} from '~/types/contact-update';
import {GraphNode} from '~/types/graph-update';
interface SubscribersProps {
notebook: Notebook;
api: GlobalApi;
groups: Groups;
book: string;
associations: Associations;
association: Association;
contacts: Rolodex;
}
export class Subscribers extends Component<SubscribersProps> {
constructor(props) {
constructor(props: SubscribersProps) {
super(props);
this.redirect = this.redirect.bind(this);
this.addUser = this.addUser.bind(this);
this.removeUser = this.removeUser.bind(this);
this.addAll = this.addAll.bind(this);
@ -33,13 +33,12 @@ export class Subscribers extends Component<SubscribersProps> {
this.props.api.groups.remove(path, [who]);
}
redirect(url) {
window.location.href = url;
}
addAll() {
const path = this.props.notebook['writers-group-path'];
const path = this.props.association['group-path'];
const group = path ? this.props.groups[path] : null;
if(!group) {
return;
}
const resource = resourceFromPath(path);
this.props.api.groups.addTag(
resource,
@ -50,7 +49,7 @@ export class Subscribers extends Component<SubscribersProps> {
render() {
const path = this.props.notebook['writers-group-path'];
const path = this.props.association['group-path'];
const group = path ? this.props.groups[path] : null;
@ -71,7 +70,10 @@ export class Subscribers extends Component<SubscribersProps> {
addDesc: 'Allow user to write to this notebook'
},
];
if(!group) {
return null;
}
return (
<div>