mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-14 17:41:33 +03:00
interface: clean up publish
This commit is contained in:
parent
0436598a99
commit
ec657cb8f8
@ -5,7 +5,7 @@ import GlobalApi from "~/logic/api/global";
|
|||||||
import { StoreState } from "~/logic/store/type";
|
import { StoreState } from "~/logic/store/type";
|
||||||
import { Association } from "~/types";
|
import { Association } from "~/types";
|
||||||
import { RouteComponentProps } from "react-router-dom";
|
import { RouteComponentProps } from "react-router-dom";
|
||||||
import { NotebookRoutes } from "./components/lib/NotebookRoutes";
|
import { NotebookRoutes } from "./components/NotebookRoutes";
|
||||||
|
|
||||||
type PublishResourceProps = StoreState & {
|
type PublishResourceProps = StoreState & {
|
||||||
association: Association;
|
association: Association;
|
||||||
|
@ -1,176 +0,0 @@
|
|||||||
import React, { useRef, useEffect } from "react";
|
|
||||||
import { Route, Switch, useLocation, withRouter } from "react-router-dom";
|
|
||||||
import { Center, Text } from "@tlon/indigo-react";
|
|
||||||
import _ from "lodash";
|
|
||||||
import Helmet from "react-helmet";
|
|
||||||
|
|
||||||
import "./css/custom.css";
|
|
||||||
|
|
||||||
import { Skeleton } from "./components/skeleton";
|
|
||||||
import { NewScreen } from "./components/lib/new";
|
|
||||||
import { JoinScreen } from "./components/lib/Join";
|
|
||||||
import { StoreState } from "~/logic/store/type";
|
|
||||||
import GlobalApi from "~/logic/api/global";
|
|
||||||
import GlobalSubscription from "~/logic/subscription/global";
|
|
||||||
import { NotebookRoutes } from "./components/lib/NotebookRoutes";
|
|
||||||
|
|
||||||
type PublishAppProps = StoreState & {
|
|
||||||
api: GlobalApi;
|
|
||||||
ship: string;
|
|
||||||
subscription: GlobalSubscription;
|
|
||||||
};
|
|
||||||
|
|
||||||
const RouterSkeleton = withRouter(Skeleton);
|
|
||||||
|
|
||||||
export default function PublishApp(props: PublishAppProps) {
|
|
||||||
const unreadTotal = useRef(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.title =
|
|
||||||
unreadTotal.current > 0
|
|
||||||
? `(${unreadTotal.current}) OS1 - Publish`
|
|
||||||
: "OS1 - Publish";
|
|
||||||
}, [unreadTotal.current]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
props.subscription.startApp("publish");
|
|
||||||
props.api.publish.fetchNotebooks();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
props.subscription.stopApp("publish");
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const contacts = props.contacts ? props.contacts : {};
|
|
||||||
|
|
||||||
const notebooks = props.notebooks ? props.notebooks : {};
|
|
||||||
|
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
unreadTotal.current = _.chain(notebooks)
|
|
||||||
.values()
|
|
||||||
.map(_.values)
|
|
||||||
.flatten() // flatten into array of notebooks
|
|
||||||
.map("num-unread")
|
|
||||||
.reduce((acc, count) => acc + count, 0)
|
|
||||||
.value();
|
|
||||||
|
|
||||||
const {
|
|
||||||
api,
|
|
||||||
groups,
|
|
||||||
sidebarShown,
|
|
||||||
invites,
|
|
||||||
associations,
|
|
||||||
hideNicknames,
|
|
||||||
hideAvatars,
|
|
||||||
remoteContentPolicy
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const active = location.pathname.endsWith("/~publish")
|
|
||||||
? "sidebar"
|
|
||||||
: "rightPanel";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Helmet defer={false}>
|
|
||||||
<title>{unreadTotal > 0 ? `(${unreadTotal}) ` : ""}OS1 - Publish</title>
|
|
||||||
</Helmet>
|
|
||||||
<Route
|
|
||||||
path={[
|
|
||||||
"/~publish/notebook/:ship/:notebook/note/:noteId",
|
|
||||||
"/~publish/notebook/:ship/:notebook/*",
|
|
||||||
"/~publish/notebook/:ship/:notebook",
|
|
||||||
"/~publish",
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<RouterSkeleton
|
|
||||||
active={active}
|
|
||||||
sidebarShown={sidebarShown}
|
|
||||||
invites={invites}
|
|
||||||
notebooks={notebooks}
|
|
||||||
associations={associations}
|
|
||||||
contacts={contacts}
|
|
||||||
api={api}
|
|
||||||
>
|
|
||||||
<Switch>
|
|
||||||
<Route exact path="/~publish">
|
|
||||||
<Center width="100%" height="100%">
|
|
||||||
<Text color="lightGray">
|
|
||||||
Select or create a notebook to begin.
|
|
||||||
</Text>
|
|
||||||
</Center>
|
|
||||||
</Route>
|
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path="/~publish/new"
|
|
||||||
render={(props) => {
|
|
||||||
return (
|
|
||||||
<NewScreen
|
|
||||||
associations={associations}
|
|
||||||
api={api}
|
|
||||||
notebooks={notebooks}
|
|
||||||
groups={groups}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path="/~publish/join/:ship/:notebook"
|
|
||||||
render={(props) => {
|
|
||||||
const ship = props.match.params.ship || "";
|
|
||||||
const notebook = props.match.params.notebook || "";
|
|
||||||
return (
|
|
||||||
<JoinScreen
|
|
||||||
notebooks={notebooks}
|
|
||||||
ship={ship}
|
|
||||||
book={notebook}
|
|
||||||
api={api}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/~publish/notebook/:ship/:notebook"
|
|
||||||
render={(props) => {
|
|
||||||
const view = props.match.params.view
|
|
||||||
? props.match.params.view
|
|
||||||
: "posts";
|
|
||||||
|
|
||||||
const ship = props.match.params.ship || "";
|
|
||||||
const book = props.match.params.notebook || "";
|
|
||||||
|
|
||||||
const bookGroupPath =
|
|
||||||
notebooks?.[ship]?.[book]?.["subscribers-group-path"];
|
|
||||||
|
|
||||||
const notebookContacts =
|
|
||||||
bookGroupPath in contacts ? contacts[bookGroupPath] : {};
|
|
||||||
|
|
||||||
const notebook = notebooks?.[ship]?.[book];
|
|
||||||
return (
|
|
||||||
<NotebookRoutes
|
|
||||||
notebook={notebook}
|
|
||||||
ship={ship}
|
|
||||||
book={book}
|
|
||||||
groups={groups}
|
|
||||||
contacts={contacts}
|
|
||||||
notebookContacts={notebookContacts}
|
|
||||||
sidebarShown={sidebarShown}
|
|
||||||
api={api}
|
|
||||||
hideNicknames={hideNicknames}
|
|
||||||
hideAvatars={hideAvatars}
|
|
||||||
remoteContentPolicy={remoteContentPolicy}
|
|
||||||
associations={associations}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Switch>
|
|
||||||
</RouterSkeleton>
|
|
||||||
</Route>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import * as Yup from "yup";
|
import * as Yup from "yup";
|
||||||
import { Formik, FormikHelpers, Form, useFormikContext } from "formik";
|
import { Formik, FormikHelpers, Form, useFormikContext } from "formik";
|
||||||
import { AsyncButton } from "../../../../components/AsyncButton";
|
import { AsyncButton } from "../../../components/AsyncButton";
|
||||||
import { ManagedTextAreaField as TextArea } from "@tlon/indigo-react";
|
import { ManagedTextAreaField as TextArea } from "@tlon/indigo-react";
|
||||||
|
|
||||||
interface FormSchema {
|
interface FormSchema {
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { AsyncButton } from "../../../../components/AsyncButton";
|
import { AsyncButton } from "../../../components/AsyncButton";
|
||||||
import * as Yup from "yup";
|
import * as Yup from "yup";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
@ -6,7 +6,7 @@ import {
|
|||||||
Row,
|
Row,
|
||||||
Col,
|
Col,
|
||||||
} from "@tlon/indigo-react";
|
} from "@tlon/indigo-react";
|
||||||
import { AsyncButton } from "../../../../components/AsyncButton";
|
import { AsyncButton } from "../../../components/AsyncButton";
|
||||||
import { Formik, Form, FormikHelpers } from "formik";
|
import { Formik, Form, FormikHelpers } from "formik";
|
||||||
import { MarkdownField } from "./MarkdownField";
|
import { MarkdownField } from "./MarkdownField";
|
||||||
|
|
@ -1,15 +1,16 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { RouteComponentProps, Link, Route, Switch } from "react-router-dom";
|
import { RouteComponentProps, Link, Route, Switch } from "react-router-dom";
|
||||||
import { Box, Text } from "@tlon/indigo-react";
|
import { Box, Text } from "@tlon/indigo-react";
|
||||||
import GlobalApi from "../../../../api/global";
|
import GlobalApi from "~/api/global";
|
||||||
import { PublishContent } from "./PublishContent";
|
import { Notebook as INotebook } from "~/types/publish-update";
|
||||||
import { Notebook as INotebook } from "../../../../types/publish-update";
|
import { Groups } from "~/types/group-update";
|
||||||
import { Groups } from "../../../../types/group-update";
|
import { Contacts, Rolodex } from "~/types/contact-update";
|
||||||
import { Contacts, Rolodex } from "../../../../types/contact-update";
|
import { LocalUpdateRemoteContentPolicy, Associations } from "~/types";
|
||||||
|
|
||||||
import Notebook from "./Notebook";
|
import Notebook from "./Notebook";
|
||||||
import NewPost from "./new-post";
|
import NewPost from "./new-post";
|
||||||
import { NoteRoutes } from './NoteRoutes';
|
import { NoteRoutes } from './NoteRoutes';
|
||||||
import { LocalUpdateRemoteContentPolicy, Associations } from "~/types";
|
|
||||||
|
|
||||||
interface NotebookRoutesProps {
|
interface NotebookRoutesProps {
|
||||||
api: GlobalApi;
|
api: GlobalApi;
|
@ -1,45 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import { Box, Text } from "@tlon/indigo-react";
|
|
||||||
import { NotebookItem } from './NotebookItem';
|
|
||||||
|
|
||||||
export class GroupItem extends Component {
|
|
||||||
render() {
|
|
||||||
const { props } = this;
|
|
||||||
const association = props.association ? props.association : {};
|
|
||||||
|
|
||||||
let title = association['app-path'] ? association['app-path'] : 'Unmanaged Notebooks';
|
|
||||||
if (association.metadata && association.metadata.title) {
|
|
||||||
title = association.metadata.title !== ''
|
|
||||||
? association.metadata.title : title;
|
|
||||||
}
|
|
||||||
|
|
||||||
const groupedBooks = props.groupedBooks ? props.groupedBooks : [];
|
|
||||||
const first = (props.index === 0) ? 'pt1' : 'pt6';
|
|
||||||
|
|
||||||
const notebookItems = groupedBooks.map((each, i) => {
|
|
||||||
const unreads = props.notebooks[each]['num-unread'] || 0;
|
|
||||||
let title = each.substr(1);
|
|
||||||
if (props.notebooks[each].title) {
|
|
||||||
title = (props.notebooks[each].title !== '')
|
|
||||||
? props.notebooks[each].title : title;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<NotebookItem
|
|
||||||
key={i}
|
|
||||||
unreadCount={unreads}
|
|
||||||
title={title}
|
|
||||||
path={each}
|
|
||||||
selected={(props.path === each)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<Box className={first}>
|
|
||||||
<Box fontSize={0} px={3} fontWeight="700" pb={2} color="lightGray">{title}</Box>
|
|
||||||
{notebookItems}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GroupItem;
|
|
@ -1,54 +0,0 @@
|
|||||||
import React, { useCallback, useState, useRef, useEffect } from "react";
|
|
||||||
import { Col, Text, ErrorLabel } from "@tlon/indigo-react";
|
|
||||||
import { Spinner } from "~/views/components/Spinner";
|
|
||||||
import { Notebooks } from "~/types/publish-update";
|
|
||||||
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
|
||||||
import { RouteComponentProps } from "react-router-dom";
|
|
||||||
import { deSig } from "~/logic/lib/util";
|
|
||||||
|
|
||||||
interface JoinScreenProps {
|
|
||||||
api: any; // GlobalApi;
|
|
||||||
ship: string;
|
|
||||||
book: string;
|
|
||||||
notebooks: Notebooks;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function JoinScreen(props: JoinScreenProps & RouteComponentProps) {
|
|
||||||
const { book, ship, api } = props;
|
|
||||||
const [error, setError] = useState(false);
|
|
||||||
const joining = useRef(false);
|
|
||||||
|
|
||||||
const waiter = useWaitForProps(props);
|
|
||||||
|
|
||||||
const onJoin = useCallback(async () => {
|
|
||||||
joining.current = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await api.publish.subscribeNotebook(deSig(ship), book);
|
|
||||||
await waiter((p) => !!p.notebooks?.[ship]?.[book]);
|
|
||||||
props.history.replace(`/~publish/notebook/${ship}/${book}`);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
setError(true);
|
|
||||||
} finally {
|
|
||||||
joining.current = false;
|
|
||||||
}
|
|
||||||
}, [waiter, api, ship, book]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (joining.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onJoin();
|
|
||||||
}, [onJoin]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Col p={4}>
|
|
||||||
<Text fontSize={1}>Joining Notebook</Text>
|
|
||||||
<Spinner awaiting text="Joining..." />
|
|
||||||
{error && <ErrorLabel>Unable to join notebook</ErrorLabel>}
|
|
||||||
</Col>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default JoinScreen;
|
|
@ -1,52 +0,0 @@
|
|||||||
import React, { Component } from "react";
|
|
||||||
import { Box, Text } from "@tlon/indigo-react";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
|
|
||||||
import { HoverBox } from "../../../../components/HoverBox";
|
|
||||||
|
|
||||||
interface NotebookItemProps {
|
|
||||||
selected: boolean;
|
|
||||||
title: string;
|
|
||||||
path: string;
|
|
||||||
unreadCount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function UnreadCount(props: { unread: number }) {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
px={1}
|
|
||||||
fontWeight="700"
|
|
||||||
py={1}
|
|
||||||
borderRadius={1}
|
|
||||||
flexShrink='0'
|
|
||||||
color="white"
|
|
||||||
bg="lightGray"
|
|
||||||
>
|
|
||||||
{props.unread}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NotebookItem(props: NotebookItemProps) {
|
|
||||||
return (
|
|
||||||
<Link to={"/~publish/notebook/" + props.path}>
|
|
||||||
<HoverBox
|
|
||||||
bg="white"
|
|
||||||
bgActive="washedGray"
|
|
||||||
selected={props.selected}
|
|
||||||
width="100%"
|
|
||||||
fontSize={0}
|
|
||||||
px={3}
|
|
||||||
py={1}
|
|
||||||
display="flex"
|
|
||||||
justifyContent="space-between"
|
|
||||||
alignItems="center"
|
|
||||||
>
|
|
||||||
<Box py='1' pr='1'>{props.title}</Box>
|
|
||||||
{props.unreadCount > 0 && <UnreadCount unread={props.unreadCount} />}
|
|
||||||
</HoverBox>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NotebookItem;
|
|
@ -1,128 +0,0 @@
|
|||||||
import React, { Component } from "react";
|
|
||||||
import { Box, Text, Col } from "@tlon/indigo-react";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import SidebarInvite from "~/views/components/Sidebar/SidebarInvite";
|
|
||||||
import { Welcome } from "./Welcome";
|
|
||||||
import { GroupItem } from "./GroupItem";
|
|
||||||
import { alphabetiseAssociations } from "~/logic/lib/util";
|
|
||||||
|
|
||||||
export function Sidebar(props: any) {
|
|
||||||
const sidebarInvites = !(props.invites && props.invites["/publish"])
|
|
||||||
? null
|
|
||||||
: Object.keys(props.invites["/publish"]).map((uid) => {
|
|
||||||
return (
|
|
||||||
<SidebarInvite
|
|
||||||
key={uid}
|
|
||||||
invite={props.invites["/publish"][uid]}
|
|
||||||
onAccept={() => props.api.invite.accept("/publish", uid)}
|
|
||||||
onDecline={() => props.api.invite.decline("/publish", uid)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
const associations =
|
|
||||||
props.associations && "contacts" in props.associations
|
|
||||||
? alphabetiseAssociations(props.associations.contacts)
|
|
||||||
: {};
|
|
||||||
|
|
||||||
const notebooks = {};
|
|
||||||
Object.keys(props.notebooks).map((host) => {
|
|
||||||
Object.keys(props.notebooks[host]).map((notebook) => {
|
|
||||||
const title = `${host}/${notebook}`;
|
|
||||||
notebooks[title] = props.notebooks[host][notebook];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const groupedNotebooks = {};
|
|
||||||
Object.keys(notebooks).map((book) => {
|
|
||||||
const path = notebooks[book]["subscribers-group-path"]
|
|
||||||
? notebooks[book]["subscribers-group-path"]
|
|
||||||
: book;
|
|
||||||
if (path in associations) {
|
|
||||||
if (groupedNotebooks[path]) {
|
|
||||||
const array = groupedNotebooks[path];
|
|
||||||
array.push(book);
|
|
||||||
groupedNotebooks[path] = array;
|
|
||||||
} else {
|
|
||||||
groupedNotebooks[path] = [book];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (groupedNotebooks["/~/"]) {
|
|
||||||
const array = groupedNotebooks["/~/"];
|
|
||||||
array.push(book);
|
|
||||||
groupedNotebooks["/~/"] = array;
|
|
||||||
} else {
|
|
||||||
groupedNotebooks["/~/"] = [book];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedGroups = props.selectedGroups ? props.selectedGroups : [];
|
|
||||||
const groupedItems = Object.keys(associations)
|
|
||||||
.map((each, i) => {
|
|
||||||
const books = groupedNotebooks[each] || [];
|
|
||||||
if (books.length === 0) return;
|
|
||||||
if (
|
|
||||||
selectedGroups.length === 0 &&
|
|
||||||
groupedNotebooks["/~/"] &&
|
|
||||||
groupedNotebooks["/~/"].length !== 0
|
|
||||||
) {
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<GroupItem
|
|
||||||
key={i}
|
|
||||||
index={i}
|
|
||||||
association={associations[each]}
|
|
||||||
groupedBooks={books}
|
|
||||||
notebooks={notebooks}
|
|
||||||
path={props.path}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
if (
|
|
||||||
selectedGroups.length === 0 &&
|
|
||||||
groupedNotebooks["/~/"] &&
|
|
||||||
groupedNotebooks["/~/"].length !== 0
|
|
||||||
) {
|
|
||||||
groupedItems.unshift(
|
|
||||||
<GroupItem
|
|
||||||
key={"/~/"}
|
|
||||||
index={0}
|
|
||||||
association={"/~/"}
|
|
||||||
groupedBooks={groupedNotebooks["/~/"]}
|
|
||||||
notebooks={notebooks}
|
|
||||||
path={props.path}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const display = props.hidden ? ['none', 'block'] : 'block';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Col
|
|
||||||
borderRight={[0, 1]}
|
|
||||||
borderRightColor={["washedGray", "washedGray"]}
|
|
||||||
height="100%"
|
|
||||||
pt={[3, 0]}
|
|
||||||
overflowY="auto"
|
|
||||||
display={display}
|
|
||||||
flexShrink={0}
|
|
||||||
width={["auto", "250px"]}
|
|
||||||
>
|
|
||||||
<Box>
|
|
||||||
<Link to="/~publish/new" className="green2 pa4 f9 dib">
|
|
||||||
<Box color="green">New Notebook</Box>
|
|
||||||
</Link>
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
className="overflow-y-auto pb1"
|
|
||||||
>
|
|
||||||
<Welcome mx={2} />
|
|
||||||
{sidebarInvites}
|
|
||||||
{groupedItems}
|
|
||||||
</Box>
|
|
||||||
</Col>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Sidebar;
|
|
@ -1,32 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { Box } from "@tlon/indigo-react";
|
|
||||||
import { useLocalStorageState } from "~/logic/lib/useLocalStorageState";
|
|
||||||
|
|
||||||
export function Welcome(props: Parameters<typeof Box>[0]) {
|
|
||||||
const [wasWelcomed, setWasWelcomed] = useLocalStorageState(
|
|
||||||
"urbit-publish:wasWelcomed",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
if (wasWelcomed) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Box {...props} p={2} border={1} >
|
|
||||||
<Box lineHeight="1.6" fontSize={0}>
|
|
||||||
Notebooks are for longer-form writing and discussion. Each Notebook is a
|
|
||||||
collection of Markdown-formatted notes with optional comments.
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
fontSize={0}
|
|
||||||
mt={2}
|
|
||||||
className="f8 pt2 dib pointer bb"
|
|
||||||
onClick={() => { setWasWelcomed(true) }}
|
|
||||||
>
|
|
||||||
Close this
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Welcome;
|
|
@ -1,103 +0,0 @@
|
|||||||
import React, { useCallback } from "react";
|
|
||||||
import { Box, ManagedTextInputField as Input, Col } from "@tlon/indigo-react";
|
|
||||||
import { Formik, Form } from "formik";
|
|
||||||
import * as Yup from "yup";
|
|
||||||
import GlobalApi from "~/logic/api/global";
|
|
||||||
import { AsyncButton } from "~/views/components/AsyncButton";
|
|
||||||
import { FormError } from "~/views/components/FormError";
|
|
||||||
import { RouteComponentProps } from "react-router-dom";
|
|
||||||
import { stringToSymbol } from "~/logic/lib/util";
|
|
||||||
import GroupSearch from "~/views/components/GroupSearch";
|
|
||||||
import { Associations } from "~/types/metadata-update";
|
|
||||||
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
|
||||||
import { Notebooks } from "~/types/publish-update";
|
|
||||||
import { Groups } from "~/types/group-update";
|
|
||||||
|
|
||||||
interface FormSchema {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
group?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formSchema = Yup.object({
|
|
||||||
name: Yup.string().required("Notebook must have a name"),
|
|
||||||
description: Yup.string(),
|
|
||||||
group: Yup.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
interface NewScreenProps {
|
|
||||||
api: GlobalApi;
|
|
||||||
associations: Associations;
|
|
||||||
notebooks: Notebooks;
|
|
||||||
groups: Groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NewScreen(props: NewScreenProps & RouteComponentProps) {
|
|
||||||
const { history } = props;
|
|
||||||
|
|
||||||
const waiter = useWaitForProps(props, 5000);
|
|
||||||
|
|
||||||
const onSubmit = async (values: FormSchema, actions) => {
|
|
||||||
const bookId = stringToSymbol(values.name);
|
|
||||||
try {
|
|
||||||
const { name, description, group } = values;
|
|
||||||
await props.api.publish.newBook(bookId, name, description, group);
|
|
||||||
await waiter((p) => !!p?.notebooks?.[`~${window.ship}`]?.[bookId]);
|
|
||||||
if (!group) {
|
|
||||||
await waiter((p) => !!p?.groups?.[`/ship/~${window.ship}/${bookId}`]);
|
|
||||||
}
|
|
||||||
actions.setStatus({ success: null });
|
|
||||||
history.push(`/~publish/notebook/~${window.ship}/${bookId}`);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
actions.setStatus({ error: "Notebook creation failed" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<Col p={3}>
|
|
||||||
<Box mb={4} color="black">New Notebook</Box>
|
|
||||||
<Formik
|
|
||||||
validationSchema={formSchema}
|
|
||||||
initialValues={{ name: "", description: "", group: "" }}
|
|
||||||
onSubmit={onSubmit}
|
|
||||||
>
|
|
||||||
<Form>
|
|
||||||
<Box
|
|
||||||
display="grid"
|
|
||||||
gridTemplateRows="auto"
|
|
||||||
gridRowGap={2}
|
|
||||||
gridTemplateColumns="300px"
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
id="name"
|
|
||||||
label="Name"
|
|
||||||
caption="Provide a name for your notebook"
|
|
||||||
placeholder="eg. My Journal"
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
id="description"
|
|
||||||
label="Description"
|
|
||||||
caption="What's your notebook about?"
|
|
||||||
placeholder="Notebook description"
|
|
||||||
/>
|
|
||||||
<GroupSearch
|
|
||||||
id="group"
|
|
||||||
label="Group"
|
|
||||||
caption="What group is the notebook for?"
|
|
||||||
associations={props.associations}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Box justifySelf="start">
|
|
||||||
<AsyncButton loadingText="Creating..." type="submit" border>
|
|
||||||
Create Notebook
|
|
||||||
</AsyncButton>
|
|
||||||
</Box>
|
|
||||||
<FormError message="Notebook Creation failed" />
|
|
||||||
</Box>
|
|
||||||
</Form>
|
|
||||||
</Formik>
|
|
||||||
</Col>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NewScreen;
|
|
@ -1,133 +0,0 @@
|
|||||||
import React, { useRef, SyntheticEvent, useEffect } from "react";
|
|
||||||
import { Box, Center } from "@tlon/indigo-react";
|
|
||||||
import { Sidebar } from "./lib/Sidebar";
|
|
||||||
import ErrorBoundary from "~/views/components/ErrorBoundary";
|
|
||||||
import { Notebooks } from "~/types/publish-update";
|
|
||||||
import { Rolodex } from "~/types/contact-update";
|
|
||||||
import { Invites } from "~/types/invite-update";
|
|
||||||
import GlobalApi from "~/logic/api/global";
|
|
||||||
import { Associations } from "~/types/metadata-update";
|
|
||||||
import { RouteComponentProps } from "react-router-dom";
|
|
||||||
|
|
||||||
type SkeletonProps = RouteComponentProps<{
|
|
||||||
ship?: string;
|
|
||||||
notebook?: string;
|
|
||||||
noteId?: string;
|
|
||||||
}> & {
|
|
||||||
notebooks: Notebooks;
|
|
||||||
invites: Invites;
|
|
||||||
associations: Associations;
|
|
||||||
contacts: Rolodex;
|
|
||||||
api: GlobalApi;
|
|
||||||
children: React.ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function Skeleton(props: SkeletonProps) {
|
|
||||||
const { api, notebooks } = props;
|
|
||||||
const { ship, notebook, noteId } = props.match.params;
|
|
||||||
const scrollRef = useRef<HTMLDivElement>();
|
|
||||||
|
|
||||||
const path =
|
|
||||||
(ship &&
|
|
||||||
notebook &&
|
|
||||||
`${props.match.params.ship}/${props.match.params.notebook}`) ||
|
|
||||||
undefined;
|
|
||||||
|
|
||||||
const onScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
|
||||||
const { scrollHeight, scrollTop, clientHeight } = e.target as HTMLDivElement;
|
|
||||||
const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
|
|
||||||
if (noteId && notebook && ship) {
|
|
||||||
const note = notebooks?.[ship]?.[notebook]?.notes?.[noteId];
|
|
||||||
if (!note || !note.comments) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const loadedComments = note.comments?.length;
|
|
||||||
const fullyLoaded = note["num-comments"] === loadedComments;
|
|
||||||
if (distanceFromBottom < 40) {
|
|
||||||
if (!fullyLoaded) {
|
|
||||||
api.publish.fetchCommentsPage(
|
|
||||||
ship,
|
|
||||||
notebook,
|
|
||||||
noteId,
|
|
||||||
loadedComments,
|
|
||||||
30
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!note.read) {
|
|
||||||
api.publish.publishAction({
|
|
||||||
read: {
|
|
||||||
who: ship.slice(1),
|
|
||||||
book: notebook,
|
|
||||||
note: noteId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (notebook && ship) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// send read immediately if we aren't in a scrollable container
|
|
||||||
useEffect(() => {
|
|
||||||
if(!(noteId && notebook && ship)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const note = notebooks?.[ship]?.[notebook]?.notes?.[noteId];
|
|
||||||
setTimeout(() => {
|
|
||||||
const { clientHeight, scrollHeight } = scrollRef.current;
|
|
||||||
const isScrolling = clientHeight < scrollHeight;
|
|
||||||
if(!isScrolling && note) {
|
|
||||||
api.publish.publishAction({
|
|
||||||
read: {
|
|
||||||
who: ship.slice(1),
|
|
||||||
book: notebook,
|
|
||||||
note: noteId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}, 1500);
|
|
||||||
}, [noteId, notebook, ship, notebooks])
|
|
||||||
|
|
||||||
const hideSidebar = path || props.location.pathname.endsWith('/new')
|
|
||||||
|
|
||||||
const panelDisplay = !hideSidebar ? ["none", "block"] : "block";
|
|
||||||
return (
|
|
||||||
<Box height="100%" width="100%" px={[0, 3]} pb={[0, 3]}>
|
|
||||||
<Box
|
|
||||||
bg="white"
|
|
||||||
display="flex"
|
|
||||||
border={[0, 1]}
|
|
||||||
borderColor={["washedGray", "washedGray"]}
|
|
||||||
borderRadius={1}
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
>
|
|
||||||
<Sidebar
|
|
||||||
notebooks={props.notebooks}
|
|
||||||
contacts={props.contacts}
|
|
||||||
path={path}
|
|
||||||
hidden={hideSidebar}
|
|
||||||
invites={props.invites}
|
|
||||||
associations={props.associations}
|
|
||||||
api={props.api}
|
|
||||||
/>
|
|
||||||
<Box
|
|
||||||
ref={scrollRef}
|
|
||||||
display={panelDisplay}
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
position="relative"
|
|
||||||
px={[3, 4]}
|
|
||||||
fontSize={0}
|
|
||||||
overflowY="scroll"
|
|
||||||
onScroll={onScroll}
|
|
||||||
>
|
|
||||||
<ErrorBoundary>{props.children}</ErrorBoundary>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Skeleton;
|
|
Loading…
Reference in New Issue
Block a user