Merge pull request #3869 from urbit/lf/hark-redux-mentions

hark, graph-store: mentions support.
This commit is contained in:
matildepark 2020-11-03 12:24:57 -05:00 committed by GitHub
commit 5618567e43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 136 additions and 38 deletions

View File

@ -211,11 +211,9 @@
^- ?
?. mentions %.n
?~ contents %.n
?. ?=(%text -.i.contents)
?. ?=(%mention -.i.contents)
$(contents t.contents)
=/ res
(find (scow %p our.bowl) (trip text.i.contents))
?^ res
?: =(our.bowl ship.i.contents)
%.y
$(contents t.contents)
::

View File

@ -74,6 +74,7 @@
|= c=^content
^- json
?- -.c
%mention (frond %mention (ship ship.c))
%text (frond %text s+text.c)
%url (frond %url s+url.c)
%reference (frond %reference (uid uid.c))
@ -324,7 +325,8 @@
::
++ content
%- of
:~ [%text so]
:~ [%mention (su ;~(pfix sig fed:ag))]
[%text so]
[%url so]
[%reference uid]
[%code eval]

View File

@ -24,7 +24,7 @@
:: comment on link post; comment text
::
[@ @ ~]
?> ?=([[%text @] ~] contents.p.ip)
?> ?=(^ contents.p.ip)
ip
==
--

View File

@ -28,6 +28,7 @@
::
+$ content
$% [%text text=cord]
[%mention =ship]
[%url url=cord]
[%code expression=cord output=(list tank)]
[%reference =uid]

View File

@ -3,10 +3,10 @@ import { StoreState } from '../store/type';
import { Patp, Path, PatpNoSig } from '~/types/noun';
import _ from 'lodash';
import {makeResource, resourceFromPath} from '../lib/group';
import {GroupPolicy, Enc, Post, NodeMap} from '~/types';
import {GroupPolicy, Enc, Post, NodeMap, Content} from '~/types';
import { numToUd, unixToDa } from '~/logic/lib/util';
export const createPost = (contents: Object[], parentIndex: string = '') => {
export const createPost = (contents: Content[], parentIndex: string = '') => {
return {
author: `~${window.ship}`,
index: parentIndex + '/' + unixToDa(Date.now()).toString(),

View File

@ -0,0 +1,24 @@
import { Content } from "~/types";
import urbitOb from "urbit-ob";
export function scanForMentions(text: string) {
const regex = /~([a-z]|-)+/g;
let result: Content[] = [];
let match: RegExpExecArray | null;
let lastPos = 0;
while ((match = regex.exec(text)) !== null) {
const newPos = match.index + match[0].length;
if (urbitOb.isValidPatp(match[0])) {
if (match.index !== lastPos) {
result.push({ text: text.slice(lastPos, match.index) });
}
result.push({ mention: match[0] });
}
lastPos = newPos;
}
const remainder = text.slice(lastPos, text.length);
if (remainder) {
result.push({ text: remainder });
}
return result;
}

View File

@ -1,12 +1,28 @@
import { Patp } from "./noun";
import { BigIntOrderedMap } from "~/logic/lib/BigIntOrderedMap";
export interface TextContent { text: string; };
export interface UrlContent { url: string; }
export interface CodeContent { expresssion: string; output: string; };
export interface ReferenceContent { uid: string; }
export type Content = TextContent | UrlContent | CodeContent | ReferenceContent;
export interface TextContent {
text: string;
}
export interface UrlContent {
url: string;
}
export interface CodeContent {
expresssion: string;
output: string;
}
export interface ReferenceContent {
uid: string;
}
export interface MentionContent {
mention: string;
}
export type Content =
| TextContent
| UrlContent
| CodeContent
| ReferenceContent
| MentionContent;
export interface Post {
author: Patp;
@ -15,10 +31,9 @@ export interface Post {
index: string;
pending?: boolean;
signatures: string[];
'time-sent': number;
"time-sent": number;
}
export interface GraphNode {
children: Graph;
post: Post;
@ -27,5 +42,3 @@ export interface GraphNode {
export type Graph = BigIntOrderedMap<GraphNode>;
export type Graphs = { [rid: string]: Graph };

View File

@ -4,9 +4,10 @@ import { cite } from '~/logic/lib/util';
import moment from 'moment';
import { Box, Text, Row } from '@tlon/indigo-react';
import RichText from '~/views/components/RichText';
import { MentionText } from "~/views/components/MentionText";
export const CommentItem = (props) => {
const content = props.post.contents[0].text;
const content = props.post.contents;
const timeSent =
moment.unix(props.post['time-sent'] / 1000).format('hh:mm a');
@ -33,10 +34,12 @@ export const CommentItem = (props) => {
</Row>
</Row>
<Row>
<Text display="block" py={3} fontSize={1}>
<RichText remoteContentPolicy={props.remoteContentPolicy}>
{content}
</RichText>
<Text py={3} fontSize={1}>
<MentionText
remoteContentPolicy={props.remoteContentPolicy}
contacts={props.contacts}
content={content}
/>
</Text>
</Row>
</Box>

View File

@ -2,6 +2,7 @@ import React, { Component } from 'react';
import { Spinner } from '~/views/components/Spinner';
import { createPost } from '~/logic/api/graph';
import { deSig } from "~/logic/lib/util";
import { scanForMentions } from "~/logic/lib/graph";
export class CommentSubmit extends Component {
@ -17,9 +18,8 @@ export class CommentSubmit extends Component {
onClickPost() {
const parentIndex = this.props.parentIndex || '';
let post = createPost([
{ text: this.state.comment },
], parentIndex);
const content = scanForMentions(this.state.comment);
let post = createPost(content, parentIndex);
this.setState({ disabled: true }, () => {
this.props.api.graph.addPost(

View File

@ -23,6 +23,7 @@ export const Comments = (props) => {
return (
<CommentItem
key={comment.post.index}
contacts={contacts}
post={comment.post}
nickname={nickname}
hasNickname={Boolean(nickname)}

View File

@ -6,14 +6,15 @@ import GlobalApi from "~/logic/api/global";
import { Box, Row } from "@tlon/indigo-react";
import styled from "styled-components";
import { Author } from "./Author";
import {GraphNode, TextContent} from "~/types/graph-update";
import tokenizeMessage from '~/logic/lib/tokenizeMessage';
import RichText from '~/views/components/RichText';
import {LocalUpdateRemoteContentPolicy} from "~/types";
import { GraphNode, TextContent } from "~/types/graph-update";
import tokenizeMessage from "~/logic/lib/tokenizeMessage";
import RichText from "~/views/components/RichText";
import { LocalUpdateRemoteContentPolicy } from "~/types";
import { MentionText } from "~/views/components/MentionText";
const ClickBox = styled(Box)`
cursor: pointer;
padding-left: ${p => p.theme.space[2]}px;
padding-left: ${(p) => p.theme.space[2]}px;
`;
interface CommentItemProps {
@ -31,9 +32,7 @@ interface CommentItemProps {
export function CommentItem(props: CommentItemProps) {
const { ship, contacts, book, api, remoteContentPolicy } = props;
const commentData = props.comment?.post;
const comment = commentData.contents[0] as TextContent;
const content = tokenizeMessage(comment.text).flat().join(' ');
const comment = commentData.contents;
const disabled = props.pending || window.ship !== commentData.author;
@ -62,7 +61,11 @@ export function CommentItem(props: CommentItemProps) {
</Author>
</Row>
<Box mb={2}>
<RichText className="f9 white-d" remoteContentPolicy={remoteContentPolicy}>{content}</RichText>
<MentionText
contacts={contacts}
content={comment}
remoteContentPolicy={remoteContentPolicy}
/>
</Box>
</Box>
);

View File

@ -11,6 +11,7 @@ import { FormikHelpers } from "formik";
import {GraphNode, Graph} from "~/types/graph-update";
import {createPost} from "~/logic/api/graph";
import { LocalUpdateRemoteContentPolicy } from "~/types";
import {scanForMentions} from "~/logic/lib/graph";
interface CommentsProps {
comments: GraphNode;
@ -32,7 +33,8 @@ export function Comments(props: CommentsProps) {
actions: FormikHelpers<{ comment: string }>
) => {
try {
const post = createPost([{ text: comment }], comments?.post?.index);
const content = scanForMentions(comment)
const post = createPost(content, comments?.post?.index);
await api.graph.addPost(ship, book, post)
actions.resetForm();
actions.setStatus({ success: null });
@ -48,7 +50,7 @@ export function Comments(props: CommentsProps) {
{Array.from(comments.children).reverse().map(([idx, comment]) => (
<CommentItem
comment={comment}
key={idx}
key={idx.toString()}
contacts={props.contacts}
api={api}
book={book}

View File

@ -0,0 +1,51 @@
import React from "react";
import _ from "lodash";
import { Text } from "@tlon/indigo-react";
import { Contacts, Content, LocalUpdateRemoteContentPolicy } from "~/types";
import RichText from "~/views/components/RichText";
import { cite } from "~/logic/lib/util";
interface MentionTextProps {
contacts: Contacts;
content: Content[];
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
}
export function MentionText(props: MentionTextProps) {
const { content, contacts } = props;
return (
<>
{_.map(content, (c, idx) => {
if ("text" in c) {
return (
<RichText
inline
key={idx}
remoteContentPolicy={props.remoteContentPolicy}
>
{c.text}
</RichText>
);
} else if ("mention" in c) {
return (
<Mention key={idx} contacts={contacts || {}} ship={c.mention} />
);
}
return null;
})}
</>
);
}
function Mention(props: { ship: string; contacts: Contacts }) {
const { contacts, ship } = props;
const contact = contacts[ship];
const showNickname = !!contact?.nickname;
const name = showNickname ? contact?.nickname : cite(ship);
return (
<Text mx="2px" px="2px" bg="washedBlue" color="blue" mono={!showNickname}>
{name}
</Text>
);
}

View File

@ -30,7 +30,7 @@ const RichText = React.memo(({ remoteContentPolicy, ...props }) => (
return <BaseAnchor target='_blank' rel='noreferrer noopener' borderBottom='1px solid' {...props}>{props.children}</BaseAnchor>;
},
paragraph: (paraProps) => {
return <Text display='block' mb='2' {...props}>{paraProps.children}</Text>;
return <Text display={props.inline ? 'inline' : 'block'} mb='2' {...props}>{paraProps.children}</Text>;
}
}}
plugins={[[