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 { 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 =

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 <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;

View File

@ -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}

View File

@ -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'
@ -127,8 +139,10 @@ 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}