mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-04 10:52:18 +03:00
Merge pull request #4018 from urbit/mp/publish/writers
publish: surface writers in new channel creation
This commit is contained in:
commit
e7154c0c53
@ -1,15 +1,11 @@
|
||||
import React, { PureComponent } from "react";
|
||||
import { Link, RouteComponentProps, Route, Switch } from "react-router-dom";
|
||||
import { NotebookPosts } from "./NotebookPosts";
|
||||
import { roleForShip } from "~/logic/lib/group";
|
||||
import { Box, Button, Text, Row, Col } from "@tlon/indigo-react";
|
||||
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 { Associations, Graph, Association } from "~/types";
|
||||
import { deSig } from "~/logic/lib/util";
|
||||
import { StatelessAsyncButton } from "~/views/components/StatelessAsyncButton";
|
||||
import React from 'react';
|
||||
import { Link, RouteComponentProps } from 'react-router-dom';
|
||||
import { NotebookPosts } from './NotebookPosts';
|
||||
import { Box, Button, Text, Row, Col } from '@tlon/indigo-react';
|
||||
import { Groups } from '~/types/group-update';
|
||||
import { Contacts, Rolodex } from '~/types/contact-update';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { Associations, Graph, Association } from '~/types';
|
||||
|
||||
interface NotebookProps {
|
||||
api: GlobalApi;
|
||||
@ -24,7 +20,6 @@ interface NotebookProps {
|
||||
hideNicknames: boolean;
|
||||
baseUrl: string;
|
||||
rootUrl: string;
|
||||
associations: Associations;
|
||||
}
|
||||
|
||||
interface NotebookState {
|
||||
@ -40,21 +35,23 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
|
||||
groups,
|
||||
hideNicknames,
|
||||
association,
|
||||
graph,
|
||||
graph
|
||||
} = props;
|
||||
const { metadata } = association;
|
||||
|
||||
const group = groups[association?.["group-path"]];
|
||||
if (!group) return null; // Waitin on groups to populate
|
||||
const group = groups[association?.['group-path']];
|
||||
if (!group) {
|
||||
return null; // Waiting on groups to populate
|
||||
};
|
||||
|
||||
const relativePath = (p: string) => props.baseUrl + p;
|
||||
|
||||
const contact = notebookContacts?.[ship];
|
||||
const role = group ? roleForShip(group, window.ship) : undefined;
|
||||
const isOwn = `~${window.ship}` === ship;
|
||||
let isWriter = true;
|
||||
|
||||
const isWriter =
|
||||
isOwn || group.tags?.publish?.[`writers-${book}`]?.has(window.ship);
|
||||
if (group.tags?.publish?.[`writers-${book}`]) {
|
||||
isWriter = isOwn || group.tags?.publish?.[`writers-${book}`]?.has(window.ship);
|
||||
}
|
||||
|
||||
const showNickname = contact?.nickname && !hideNicknames;
|
||||
|
||||
@ -62,16 +59,15 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
|
||||
<Col gapY="4" pt={4} mx="auto" px={3} maxWidth="768px">
|
||||
<Row justifyContent="space-between">
|
||||
<Box>
|
||||
<Text> {metadata?.title}</Text>
|
||||
<br />
|
||||
<Text display='block'>{metadata?.title}</Text>
|
||||
<Text color="lightGray">by </Text>
|
||||
<Text fontFamily={showNickname ? "sans" : "mono"}>
|
||||
<Text fontFamily={showNickname ? 'sans' : 'mono'}>
|
||||
{showNickname ? contact?.nickname : ship}
|
||||
</Text>
|
||||
</Box>
|
||||
{isWriter && (
|
||||
<Link to={relativePath("/new")}>
|
||||
<Button primary style={{ cursor: "pointer" }}>
|
||||
<Link to={relativePath('/new')}>
|
||||
<Button primary style={{ cursor: 'pointer' }}>
|
||||
New Post
|
||||
</Button>
|
||||
</Link>
|
||||
@ -82,7 +78,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
|
||||
graph={graph}
|
||||
host={ship}
|
||||
book={book}
|
||||
contacts={!!notebookContacts ? notebookContacts : {}}
|
||||
contacts={notebookContacts ? notebookContacts : {}}
|
||||
hideNicknames={hideNicknames}
|
||||
baseUrl={props.baseUrl}
|
||||
/>
|
||||
|
@ -4,6 +4,7 @@ import { ShipSearch } from '~/views/components/ShipSearch';
|
||||
import { Formik, Form, FormikHelpers } from 'formik';
|
||||
import { resourceFromPath } from '~/logic/lib/group';
|
||||
import { AsyncButton } from '~/views/components/AsyncButton';
|
||||
import { cite } from '~/logic/lib/util';
|
||||
|
||||
export class Writers extends Component {
|
||||
render() {
|
||||
@ -27,6 +28,7 @@ export class Writers extends Component {
|
||||
actions.setStatus({ error: e.message });
|
||||
}
|
||||
};
|
||||
const writers = Array.from(groups?.[association?.['group-path']]?.tags.publish?.[`writers-${name}`] || new Set()).map(e => cite(`~${e}`)).join(', ');
|
||||
|
||||
return (
|
||||
<Box maxWidth='512px'>
|
||||
@ -49,6 +51,10 @@ export class Writers extends Component {
|
||||
</AsyncButton>
|
||||
</Form>
|
||||
</Formik>
|
||||
{writers.length > 0 && <>
|
||||
<Text display='block' mt='2'>Current writers:</Text>
|
||||
<Text mt='2' display='block' mono>{writers}</Text>
|
||||
</>}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -1,37 +1,42 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
ManagedTextInputField as Input,
|
||||
Col,
|
||||
ManagedRadioButtonField as Radio,
|
||||
Text,
|
||||
} 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, parentPath } from "~/logic/lib/util";
|
||||
import GroupSearch from "~/views/components/GroupSearch";
|
||||
import { Associations } from "~/types/metadata-update";
|
||||
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
||||
import { Groups } from "~/types/group-update";
|
||||
import { ShipSearch } from "~/views/components/ShipSearch";
|
||||
import { Rolodex, Workspace } from "~/types";
|
||||
Icon,
|
||||
Row
|
||||
} 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, parentPath } from '~/logic/lib/util';
|
||||
import { resourceFromPath } from '~/logic/lib/group';
|
||||
import { Associations } from '~/types/metadata-update';
|
||||
import { useWaitForProps } from '~/logic/lib/useWaitForProps';
|
||||
import { Groups } from '~/types/group-update';
|
||||
import { ShipSearch } from '~/views/components/ShipSearch';
|
||||
import { Rolodex, Workspace } from '~/types';
|
||||
|
||||
interface FormSchema {
|
||||
name: string;
|
||||
description: string;
|
||||
ships: string[];
|
||||
moduleType: "chat" | "publish" | "link";
|
||||
moduleType: 'chat' | 'publish' | 'link';
|
||||
writers: string[];
|
||||
}
|
||||
|
||||
const formSchema = Yup.object({
|
||||
const formSchema = (group, groups) => Yup.object({
|
||||
name: Yup.string().required('Channel must have a name'),
|
||||
description: Yup.string(),
|
||||
ships: Yup.array(Yup.string()),
|
||||
moduleType: Yup.string().required('Must choose channel type')
|
||||
moduleType: Yup.string().required('Must choose channel type'),
|
||||
writers: Yup.array(Yup.string().test('ingroup', 'Writers must be in group',
|
||||
value => groups?.[group]?.members?.has(value)))
|
||||
});
|
||||
|
||||
interface NewChannelProps {
|
||||
@ -45,136 +50,172 @@ interface NewChannelProps {
|
||||
}
|
||||
|
||||
export function NewChannel(props: NewChannelProps & RouteComponentProps) {
|
||||
const { history, api, group, workspace } = props;
|
||||
const { history, api, group, workspace, groups } = props;
|
||||
|
||||
const waiter = useWaitForProps(props, 5000);
|
||||
|
||||
const onSubmit = async (values: FormSchema, actions) => {
|
||||
const resId: string = stringToSymbol(values.name)
|
||||
+ ((workspace?.type !== 'home') ? `-${Math.floor(Math.random() * 10000)}`
|
||||
: '');
|
||||
+ ((workspace?.type !== 'home') ? `-${Math.floor(Math.random() * 10000)}`
|
||||
: '');
|
||||
try {
|
||||
const { name, description, moduleType, ships } = values;
|
||||
const { name, description, moduleType, ships, writers } = values;
|
||||
switch (moduleType) {
|
||||
case 'chat':
|
||||
const appPath = `/~${window.ship}/${resId}`;
|
||||
const groupPath = group || `/ship${appPath}`;
|
||||
const appPath = `/~${window.ship}/${resId}`;
|
||||
const groupPath = group || `/ship${appPath}`;
|
||||
|
||||
await api.chat.create(
|
||||
name,
|
||||
description,
|
||||
appPath,
|
||||
groupPath,
|
||||
{ invite: { pending: ships.map(s => `~${s}`) } },
|
||||
ships.map(s => `~${s}`),
|
||||
true,
|
||||
false
|
||||
await api.chat.create(
|
||||
name,
|
||||
description,
|
||||
appPath,
|
||||
groupPath,
|
||||
{ invite: { pending: ships.map(s => `~${s}`) } },
|
||||
ships.map(s => `~${s}`),
|
||||
true,
|
||||
false
|
||||
);
|
||||
break;
|
||||
case "publish":
|
||||
case "link":
|
||||
if (group) {
|
||||
await api.graph.createManagedGraph(
|
||||
resId,
|
||||
name,
|
||||
description,
|
||||
group,
|
||||
moduleType
|
||||
);
|
||||
} else {
|
||||
await api.graph.createUnmanagedGraph(
|
||||
resId,
|
||||
name,
|
||||
description,
|
||||
{ invite: { pending: ships.map((s) => `~${s}`) } },
|
||||
moduleType
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log('fallthrough');
|
||||
}
|
||||
case 'publish':
|
||||
if (writers.length > 0) {
|
||||
const resource = resourceFromPath(group);
|
||||
await api.groups.addTag(
|
||||
resource,
|
||||
{ app: 'publish', tag: `writers-${resId}` },
|
||||
writers.map(s => `~${s}`)
|
||||
);
|
||||
}
|
||||
case 'link':
|
||||
if (group) {
|
||||
await api.graph.createManagedGraph(
|
||||
resId,
|
||||
name,
|
||||
description,
|
||||
group,
|
||||
moduleType
|
||||
);
|
||||
} else {
|
||||
await api.graph.createUnmanagedGraph(
|
||||
resId,
|
||||
name,
|
||||
description,
|
||||
{ invite: { pending: ships.map(s => `~${s}`) } },
|
||||
moduleType
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log('fallthrough');
|
||||
}
|
||||
|
||||
if (!group) {
|
||||
await waiter(p => Boolean(p?.groups?.[`/ship/~${window.ship}/${resId}`]));
|
||||
}
|
||||
if (moduleType === 'chat') {
|
||||
await waiter(p => Boolean(p?.chatSynced?.[`/~${window.ship}/${resId}`]));
|
||||
}
|
||||
actions.setStatus({ success: null });
|
||||
const resourceUrl = parentPath(location.pathname);
|
||||
history.push(
|
||||
`${resourceUrl}/resource/${moduleType}` +
|
||||
`${moduleType !== 'chat' ? '/ship' : ''}/~${window.ship}/${resId}`
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
actions.setStatus({ error: 'Channel creation failed' });
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Col overflowY="auto" p={3}>
|
||||
<Box pb='3' display={['block', 'none']} onClick={() => history.push(props.baseUrl)}>
|
||||
{'<- Back'}
|
||||
</Box>
|
||||
<Box fontWeight="bold" mb={4} color="black">
|
||||
New Channel
|
||||
</Box>
|
||||
<Formik
|
||||
validationSchema={formSchema}
|
||||
initialValues={{
|
||||
moduleType: 'chat',
|
||||
name: '',
|
||||
description: '',
|
||||
group: '',
|
||||
ships: []
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form>
|
||||
<Col
|
||||
maxWidth="348px"
|
||||
gapY="4"
|
||||
>
|
||||
<Col gapY="2">
|
||||
<Box color="black" mb={2}>Channel Type</Box>
|
||||
<Radio label="Chat" id="chat" name="moduleType" />
|
||||
<Radio label="Notebook" id="publish" name="moduleType" />
|
||||
<Radio label="Collection" id="link" name="moduleType" />
|
||||
</Col>
|
||||
<Input
|
||||
id="name"
|
||||
label="Name"
|
||||
caption="Provide a name for your channel"
|
||||
placeholder="eg. My Channel"
|
||||
/>
|
||||
<Input
|
||||
id="description"
|
||||
label="Description"
|
||||
caption="What's your channel about?"
|
||||
placeholder="Channel description"
|
||||
/>
|
||||
{(workspace?.type === 'home') &&
|
||||
<ShipSearch
|
||||
groups={props.groups}
|
||||
contacts={props.contacts}
|
||||
id="ships"
|
||||
label="Invitees"
|
||||
/>}
|
||||
<Box justifySelf="start">
|
||||
<AsyncButton
|
||||
primary
|
||||
loadingText="Creating..."
|
||||
type="submit"
|
||||
border
|
||||
>
|
||||
Create Channel
|
||||
</AsyncButton>
|
||||
</Box>
|
||||
<FormError message="Channel creation failed" />
|
||||
</Col>
|
||||
</Form>
|
||||
</Formik>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
if (!group) {
|
||||
await waiter(p => Boolean(p?.groups?.[`/ship/~${window.ship}/${resId}`]));
|
||||
}
|
||||
if (moduleType === 'chat') {
|
||||
await waiter(p => Boolean(p?.chatSynced?.[`/~${window.ship}/${resId}`]));
|
||||
}
|
||||
actions.setStatus({ success: null });
|
||||
const resourceUrl = parentPath(location.pathname);
|
||||
history.push(
|
||||
`${resourceUrl}/resource/${moduleType}` +
|
||||
`${moduleType !== 'chat' ? '/ship' : ''}/~${window.ship}/${resId}`
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
actions.setStatus({ error: 'Channel creation failed' });
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Col overflowY="auto" p={3}>
|
||||
<Box pb='3' display={['block', 'none']} onClick={() => history.push(props.baseUrl)}>
|
||||
{'<- Back'}
|
||||
</Box>
|
||||
<Box fontWeight="bold" mb={4} color="black">
|
||||
New Channel
|
||||
</Box>
|
||||
<Formik
|
||||
validationSchema={formSchema(group, groups)}
|
||||
initialValues={{
|
||||
moduleType: 'chat',
|
||||
name: '',
|
||||
description: '',
|
||||
group: '',
|
||||
ships: [],
|
||||
writers: []
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{ ({ errors, values }) => <Form>
|
||||
<Col
|
||||
maxWidth="348px"
|
||||
gapY="4"
|
||||
>
|
||||
<Col gapY="2">
|
||||
<Box color="black" mb={2}>Channel Type</Box>
|
||||
<Radio label="Chat" id="chat" name="moduleType" />
|
||||
<Radio label="Notebook" id="publish" name="moduleType" />
|
||||
<Radio label="Collection" id="link" name="moduleType" />
|
||||
</Col>
|
||||
<Input
|
||||
id="name"
|
||||
label="Name"
|
||||
caption="Provide a name for your channel"
|
||||
placeholder="eg. My Channel"
|
||||
/>
|
||||
<Input
|
||||
id="description"
|
||||
label="Description"
|
||||
caption="What's your channel about?"
|
||||
placeholder="Channel description"
|
||||
/>
|
||||
{(workspace?.type === 'home') &&
|
||||
<ShipSearch
|
||||
groups={props.groups}
|
||||
contacts={props.contacts}
|
||||
id="ships"
|
||||
label="Invitees"
|
||||
/>}
|
||||
{(workspace?.type !== 'home' && values.moduleType === 'publish') &&
|
||||
<>
|
||||
<ShipSearch
|
||||
groups={props.groups}
|
||||
contacts={props.contacts}
|
||||
caption="Add writers to restrict who can write to this
|
||||
notebook, or leave blank to allow all group members to write"
|
||||
id="writers"
|
||||
label="Writers"
|
||||
/>
|
||||
{errors.writers &&
|
||||
<>
|
||||
<Row>
|
||||
<Icon
|
||||
color='white'
|
||||
mr='2'
|
||||
backgroundColor='red'
|
||||
borderRadius='999px'
|
||||
icon="ExclaimationMarkBold"
|
||||
/>
|
||||
<Text color='red'>
|
||||
{Array.from(new Set([...errors.writers]))}
|
||||
</Text>
|
||||
</Row>
|
||||
</>
|
||||
}
|
||||
</>}
|
||||
<Box justifySelf="start">
|
||||
<AsyncButton
|
||||
primary
|
||||
loadingText="Creating..."
|
||||
type="submit"
|
||||
border
|
||||
>
|
||||
Create Channel
|
||||
</AsyncButton>
|
||||
</Box>
|
||||
<FormError message="Channel creation failed" />
|
||||
</Col>
|
||||
</Form>}
|
||||
</Formik>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user