mirror of
https://github.com/urbit/shrub.git
synced 2024-12-24 20:47:27 +03:00
graph: permalinks
This commit is contained in:
parent
f81e36317a
commit
5d43031f3d
@ -4,16 +4,38 @@ import { Center, Text } from "@tlon/indigo-react";
|
|||||||
import { deSig } from '~/logic/lib/util';
|
import { deSig } from '~/logic/lib/util';
|
||||||
import useGraphState from '~/logic/state/graph';
|
import useGraphState from '~/logic/state/graph';
|
||||||
import useMetadataState from '~/logic/state/metadata';
|
import useMetadataState from '~/logic/state/metadata';
|
||||||
|
import useGroupState from '~/logic/state/group';
|
||||||
|
import { GraphIndexRoute } from './graphIndex';
|
||||||
|
|
||||||
const GraphApp = (props) => {
|
const GraphApp = (props) => {
|
||||||
const associations= useMetadataState(state => state.associations);
|
const associations= useMetadataState(state => state.associations);
|
||||||
const graphKeys = useGraphState(state => state.graphKeys);
|
const graphKeys = useGraphState(state => state.graphKeys);
|
||||||
|
const groups = useGroupState(state => state.groups);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const { api } = props;
|
const { api } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
|
<Route path="/~graph/graph/ship/:ship/:name"
|
||||||
|
render={(props) => {
|
||||||
|
const resource =
|
||||||
|
`${deSig(props.match.params.ship)}/${props.match.params.name}`;
|
||||||
|
const { ship, name } = props.match.params;
|
||||||
|
const path = `/ship/~${deSig(ship)}/${name}`;
|
||||||
|
const association = associations.graph[path];
|
||||||
|
const url = `/~graph/graph/ship/${ship}/${name}`;
|
||||||
|
const group = groups[association.group];
|
||||||
|
if(!(group && association)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GraphIndexRoute url={url} association={association} index="" group={group} />
|
||||||
|
);
|
||||||
|
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Route exact path="/~graph/join/ship/:ship/:name/:module?"
|
<Route exact path="/~graph/join/ship/:ship/:name/:module?"
|
||||||
render={(props) => {
|
render={(props) => {
|
||||||
const resource =
|
const resource =
|
||||||
@ -52,4 +74,4 @@ const GraphApp = (props) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GraphApp;
|
export default GraphApp;
|
||||||
|
122
pkg/interface/src/views/apps/graph/graphIndex.tsx
Normal file
122
pkg/interface/src/views/apps/graph/graphIndex.tsx
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import React from "react";
|
||||||
|
import _ from "lodash";
|
||||||
|
import { Switch, Route, Redirect } from "react-router-dom";
|
||||||
|
import { Association, Group } from "@urbit/api";
|
||||||
|
|
||||||
|
export function getGraphPermalink(
|
||||||
|
assoc: Association,
|
||||||
|
group: Group,
|
||||||
|
index: string
|
||||||
|
) {
|
||||||
|
const mod = assoc.metadata.module;
|
||||||
|
const groupPath = group.hidden
|
||||||
|
? "/~/landscape/home"
|
||||||
|
: `/~landscape${assoc.group}`;
|
||||||
|
if (mod === "chat") {
|
||||||
|
return getChatPermalink(
|
||||||
|
group.hidden ? "/~landscape/messages" : `/~landscape${assoc.group}`,
|
||||||
|
assoc,
|
||||||
|
index
|
||||||
|
);
|
||||||
|
} else if (mod === "publish") {
|
||||||
|
return getPublishPermalink(groupPath, assoc, index);
|
||||||
|
} else if (mod === "link") {
|
||||||
|
return getLinkPermalink(groupPath, assoc, index);
|
||||||
|
}
|
||||||
|
return "/~404";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPublishPermalink(
|
||||||
|
groupPath: string,
|
||||||
|
assoc: Association,
|
||||||
|
index: string
|
||||||
|
) {
|
||||||
|
const idx = index.split("/").slice(1);
|
||||||
|
const base = `${groupPath}/resource/publish${assoc.resource}`;
|
||||||
|
let isComment = false;
|
||||||
|
const res = _.reduce(
|
||||||
|
idx,
|
||||||
|
(acc, val, i) => {
|
||||||
|
if (i === 0) {
|
||||||
|
return {...acc, pathname: `${acc.pathname}/note/${val}` };
|
||||||
|
} else if (i === 1 && val === '2') {
|
||||||
|
isComment = true;
|
||||||
|
return acc;
|
||||||
|
} else if (i === 2 && isComment) {
|
||||||
|
return { ...acc, search: `?selected=${val}` };
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ pathname: base }
|
||||||
|
);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLinkPermalink(
|
||||||
|
groupPath: string,
|
||||||
|
assoc: Association,
|
||||||
|
index: string
|
||||||
|
) {
|
||||||
|
const idx = index.split("/").slice(1);
|
||||||
|
const base = `${groupPath}/resource/link${assoc.resource}`;
|
||||||
|
const res = _.reduce(
|
||||||
|
idx,
|
||||||
|
(acc, val, i) => {
|
||||||
|
console.log(acc);
|
||||||
|
if (i === 0) {
|
||||||
|
return {...acc, pathname: `${acc.pathname}/${val}` };
|
||||||
|
} else if (i === 1) {
|
||||||
|
return {...acc, search: `?selected=${val}` };
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ pathname: base }
|
||||||
|
);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChatPermalink(
|
||||||
|
groupPath: string,
|
||||||
|
assoc: Association,
|
||||||
|
index: string
|
||||||
|
) {
|
||||||
|
const idx = index.split("/").slice(1);
|
||||||
|
if (idx.length === 0) {
|
||||||
|
return `${groupPath}/resource/chat${assoc.resource}`;
|
||||||
|
}
|
||||||
|
return `${groupPath}/resource/chat${assoc.resource}?msg=${idx[0]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GraphIndexRoute(props: {
|
||||||
|
association: Association;
|
||||||
|
group: Group;
|
||||||
|
index: string;
|
||||||
|
url: string;
|
||||||
|
}) {
|
||||||
|
const { url, index, association, group } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Switch>
|
||||||
|
<Route
|
||||||
|
path={`${url}/:id`}
|
||||||
|
render={({ match }) => {
|
||||||
|
const newUrl = `${url}/${match.params.id}`;
|
||||||
|
const newIndex = `${index}/${match.params.id}`;
|
||||||
|
return (
|
||||||
|
<GraphIndexRoute
|
||||||
|
group={group}
|
||||||
|
url={newUrl}
|
||||||
|
association={association}
|
||||||
|
index={newIndex}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Route path="">
|
||||||
|
<Redirect
|
||||||
|
to={getGraphPermalink(association, group, index)}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
);
|
||||||
|
}
|
@ -82,7 +82,7 @@ export function LinkResource(props: LinkResourceProps) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={relativePath('/:index(\\d+)/:commentId?')}
|
path={relativePath('/:index')}
|
||||||
render={(props) => {
|
render={(props) => {
|
||||||
const index = bigInt(props.match.params.index);
|
const index = bigInt(props.match.params.index);
|
||||||
const editCommentId = props.match.params.commentId || null;
|
const editCommentId = props.match.params.commentId || null;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, {useEffect, useRef} from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
@ -27,10 +27,12 @@ interface CommentItemProps {
|
|||||||
ship: string;
|
ship: string;
|
||||||
api: GlobalApi;
|
api: GlobalApi;
|
||||||
group: Group;
|
group: Group;
|
||||||
|
highlighted: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CommentItem(props: CommentItemProps): ReactElement {
|
export function CommentItem(props: CommentItemProps): ReactElement {
|
||||||
const { ship, name, api, comment, group } = props;
|
const { ship, name, api, comment, group } = props;
|
||||||
|
const ref = useRef<HTMLElement | null>(null);
|
||||||
const [, post] = getLatestCommentRevision(comment);
|
const [, post] = getLatestCommentRevision(comment);
|
||||||
const disabled = props.pending;
|
const disabled = props.pending;
|
||||||
|
|
||||||
@ -40,13 +42,12 @@ export function CommentItem(props: CommentItemProps): ReactElement {
|
|||||||
|
|
||||||
const commentIndexArray = (comment.post?.index || '/').split('/');
|
const commentIndexArray = (comment.post?.index || '/').split('/');
|
||||||
const commentIndex = commentIndexArray[commentIndexArray.length - 1];
|
const commentIndex = commentIndexArray[commentIndexArray.length - 1];
|
||||||
const updateUrl = `${props.baseUrl}/${commentIndex}`;
|
|
||||||
|
|
||||||
const adminLinks: JSX.Element[] = [];
|
const adminLinks: JSX.Element[] = [];
|
||||||
const ourRole = roleForShip(group, window.ship);
|
const ourRole = roleForShip(group, window.ship);
|
||||||
if (window.ship == post?.author && !disabled) {
|
if (window.ship == post?.author && !disabled) {
|
||||||
adminLinks.push(
|
adminLinks.push(
|
||||||
<Link to={updateUrl}>
|
<Link to={{ pathname: props.baseUrl, search: `?edit=${commentIndex}`}}>
|
||||||
<Text
|
<Text
|
||||||
color="blue"
|
color="blue"
|
||||||
ml={2}
|
ml={2}
|
||||||
@ -65,9 +66,16 @@ export function CommentItem(props: CommentItemProps): ReactElement {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(props.highlighted) {
|
||||||
|
ref.current.scrollIntoView();
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [props.highlighted]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box mb={4} opacity={post?.pending ? '60%' : '100%'}>
|
<Box ref={ref} border={props.highlighted ? 1 : 0} borderRadius={1} borderColor="blue" mb={4} opacity={post?.pending ? '60%' : '100%'}>
|
||||||
<Row bg="white" my={3}>
|
<Row my={3}>
|
||||||
<Author
|
<Author
|
||||||
showImage
|
showImage
|
||||||
ship={post?.author}
|
ship={post?.author}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
import bigInt from 'big-integer';
|
import bigInt from 'big-integer';
|
||||||
import { Col } from '@tlon/indigo-react';
|
import { Col } from '@tlon/indigo-react';
|
||||||
import { CommentItem } from './CommentItem';
|
import { CommentItem } from './CommentItem';
|
||||||
@ -14,6 +14,7 @@ import { getUnreadCount } from '~/logic/lib/hark';
|
|||||||
import { PropFunc } from '~/types/util';
|
import { PropFunc } from '~/types/util';
|
||||||
import { isWriter } from '~/logic/lib/group';
|
import { isWriter } from '~/logic/lib/group';
|
||||||
import useHarkState from '~/logic/state/hark';
|
import useHarkState from '~/logic/state/hark';
|
||||||
|
import {useQuery} from '~/logic/lib/useQuery';
|
||||||
|
|
||||||
interface CommentsProps {
|
interface CommentsProps {
|
||||||
comments: GraphNode;
|
comments: GraphNode;
|
||||||
@ -32,7 +33,6 @@ export function Comments(props: CommentsProps & PropFunc<typeof Col>) {
|
|||||||
comments,
|
comments,
|
||||||
ship,
|
ship,
|
||||||
name,
|
name,
|
||||||
editCommentId,
|
|
||||||
api,
|
api,
|
||||||
history,
|
history,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
@ -40,6 +40,18 @@ export function Comments(props: CommentsProps & PropFunc<typeof Col>) {
|
|||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const { query } = useQuery();
|
||||||
|
const selectedComment = useMemo(() => {
|
||||||
|
const id = query.get('selected')
|
||||||
|
return id ? bigInt(id) : null;
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
|
const editCommentId = useMemo(() => {
|
||||||
|
const id = query.get('edit')
|
||||||
|
return id || '';
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
|
|
||||||
const onSubmit = async (
|
const onSubmit = async (
|
||||||
{ comment },
|
{ comment },
|
||||||
actions: FormikHelpers<{ comment: string }>
|
actions: FormikHelpers<{ comment: string }>
|
||||||
@ -116,8 +128,8 @@ export function Comments(props: CommentsProps & PropFunc<typeof Col>) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Col {...rest}>
|
<Col {...rest}>
|
||||||
{( !props.editCommentId && canComment ? <CommentInput onSubmit={onSubmit} /> : null )}
|
{( !editCommentId && canComment ? <CommentInput onSubmit={onSubmit} /> : null )}
|
||||||
{( props.editCommentId ? (
|
{( editCommentId ? (
|
||||||
<CommentInput
|
<CommentInput
|
||||||
onSubmit={onEdit}
|
onSubmit={onEdit}
|
||||||
label='Edit Comment'
|
label='Edit Comment'
|
||||||
@ -126,9 +138,11 @@ export function Comments(props: CommentsProps & PropFunc<typeof Col>) {
|
|||||||
/>
|
/>
|
||||||
) : null )}
|
) : null )}
|
||||||
{children.reverse()
|
{children.reverse()
|
||||||
.map(([idx, comment], i) => {
|
.map(([idx, comment], i) => {
|
||||||
|
const highlighted = selectedComment?.eq(idx) ?? false;
|
||||||
return (
|
return (
|
||||||
<CommentItem
|
<CommentItem
|
||||||
|
highlighted={highlighted}
|
||||||
comment={comment}
|
comment={comment}
|
||||||
key={idx.toString()}
|
key={idx.toString()}
|
||||||
api={api}
|
api={api}
|
||||||
|
Loading…
Reference in New Issue
Block a user