graph: permalinks

This commit is contained in:
Liam Fitzgerald 2021-03-16 12:10:48 +10:00
parent f81e36317a
commit 5d43031f3d
No known key found for this signature in database
GPG Key ID: D390E12C61D1CFFB
5 changed files with 178 additions and 12 deletions

View File

@ -4,16 +4,38 @@ import { Center, Text } from "@tlon/indigo-react";
import { deSig } from '~/logic/lib/util';
import useGraphState from '~/logic/state/graph';
import useMetadataState from '~/logic/state/metadata';
import useGroupState from '~/logic/state/group';
import { GraphIndexRoute } from './graphIndex';
const GraphApp = (props) => {
const associations= useMetadataState(state => state.associations);
const graphKeys = useGraphState(state => state.graphKeys);
const groups = useGroupState(state => state.groups);
const history = useHistory();
const { api } = props;
return (
<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?"
render={(props) => {
const resource =
@ -52,4 +74,4 @@ const GraphApp = (props) => {
);
}
export default GraphApp;
export default GraphApp;

View 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>
);
}

View File

@ -82,7 +82,7 @@ export function LinkResource(props: LinkResourceProps) {
}}
/>
<Route
path={relativePath('/:index(\\d+)/:commentId?')}
path={relativePath('/:index')}
render={(props) => {
const index = bigInt(props.match.params.index);
const editCommentId = props.match.params.commentId || null;

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, {useEffect, useRef} from 'react';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
@ -27,10 +27,12 @@ interface CommentItemProps {
ship: string;
api: GlobalApi;
group: Group;
highlighted: boolean;
}
export function CommentItem(props: CommentItemProps): ReactElement {
const { ship, name, api, comment, group } = props;
const ref = useRef<HTMLElement | null>(null);
const [, post] = getLatestCommentRevision(comment);
const disabled = props.pending;
@ -40,13 +42,12 @@ export function CommentItem(props: CommentItemProps): ReactElement {
const commentIndexArray = (comment.post?.index || '/').split('/');
const commentIndex = commentIndexArray[commentIndexArray.length - 1];
const updateUrl = `${props.baseUrl}/${commentIndex}`;
const adminLinks: JSX.Element[] = [];
const ourRole = roleForShip(group, window.ship);
if (window.ship == post?.author && !disabled) {
adminLinks.push(
<Link to={updateUrl}>
<Link to={{ pathname: props.baseUrl, search: `?edit=${commentIndex}`}}>
<Text
color="blue"
ml={2}
@ -65,9 +66,16 @@ export function CommentItem(props: CommentItemProps): ReactElement {
)
};
useEffect(() => {
if(props.highlighted) {
ref.current.scrollIntoView();
}
}, [props.highlighted]);
return (
<Box mb={4} opacity={post?.pending ? '60%' : '100%'}>
<Row bg="white" my={3}>
<Box ref={ref} border={props.highlighted ? 1 : 0} borderRadius={1} borderColor="blue" mb={4} opacity={post?.pending ? '60%' : '100%'}>
<Row my={3}>
<Author
showImage
ship={post?.author}

View File

@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React, { useEffect, useMemo } from 'react';
import bigInt from 'big-integer';
import { Col } from '@tlon/indigo-react';
import { CommentItem } from './CommentItem';
@ -14,6 +14,7 @@ import { getUnreadCount } from '~/logic/lib/hark';
import { PropFunc } from '~/types/util';
import { isWriter } from '~/logic/lib/group';
import useHarkState from '~/logic/state/hark';
import {useQuery} from '~/logic/lib/useQuery';
interface CommentsProps {
comments: GraphNode;
@ -32,7 +33,6 @@ export function Comments(props: CommentsProps & PropFunc<typeof Col>) {
comments,
ship,
name,
editCommentId,
api,
history,
baseUrl,
@ -40,6 +40,18 @@ export function Comments(props: CommentsProps & PropFunc<typeof Col>) {
...rest
} = 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 (
{ comment },
actions: FormikHelpers<{ comment: string }>
@ -116,8 +128,8 @@ export function Comments(props: CommentsProps & PropFunc<typeof Col>) {
return (
<Col {...rest}>
{( !props.editCommentId && canComment ? <CommentInput onSubmit={onSubmit} /> : null )}
{( props.editCommentId ? (
{( !editCommentId && canComment ? <CommentInput onSubmit={onSubmit} /> : null )}
{( editCommentId ? (
<CommentInput
onSubmit={onEdit}
label='Edit Comment'
@ -126,9 +138,11 @@ export function Comments(props: CommentsProps & PropFunc<typeof Col>) {
/>
) : null )}
{children.reverse()
.map(([idx, comment], i) => {
.map(([idx, comment], i) => {
const highlighted = selectedComment?.eq(idx) ?? false;
return (
<CommentItem
highlighted={highlighted}
comment={comment}
key={idx.toString()}
api={api}