diff --git a/pkg/arvo/app/hark-graph-hook.hoon b/pkg/arvo/app/hark-graph-hook.hoon index 7c97dc64e..1f0bf1d51 100644 --- a/pkg/arvo/app/hark-graph-hook.hoon +++ b/pkg/arvo/app/hark-graph-hook.hoon @@ -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) :: diff --git a/pkg/arvo/lib/graph-store.hoon b/pkg/arvo/lib/graph-store.hoon index ecb423994..98eaa8fc6 100644 --- a/pkg/arvo/lib/graph-store.hoon +++ b/pkg/arvo/lib/graph-store.hoon @@ -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] diff --git a/pkg/arvo/mar/graph/validator/link.hoon b/pkg/arvo/mar/graph/validator/link.hoon index 839896b8f..69087ca1c 100644 --- a/pkg/arvo/mar/graph/validator/link.hoon +++ b/pkg/arvo/mar/graph/validator/link.hoon @@ -24,7 +24,7 @@ :: comment on link post; comment text :: [@ @ ~] - ?> ?=([[%text @] ~] contents.p.ip) + ?> ?=(^ contents.p.ip) ip == -- diff --git a/pkg/arvo/sur/post.hoon b/pkg/arvo/sur/post.hoon index c5c354615..4855a3a68 100644 --- a/pkg/arvo/sur/post.hoon +++ b/pkg/arvo/sur/post.hoon @@ -28,6 +28,7 @@ :: +$ content $% [%text text=cord] + [%mention =ship] [%url url=cord] [%code expression=cord output=(list tank)] [%reference =uid] diff --git a/pkg/interface/src/logic/api/graph.ts b/pkg/interface/src/logic/api/graph.ts index fe73cac0b..456776f42 100644 --- a/pkg/interface/src/logic/api/graph.ts +++ b/pkg/interface/src/logic/api/graph.ts @@ -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(), diff --git a/pkg/interface/src/logic/lib/graph.ts b/pkg/interface/src/logic/lib/graph.ts new file mode 100644 index 000000000..b17848ded --- /dev/null +++ b/pkg/interface/src/logic/lib/graph.ts @@ -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; +} diff --git a/pkg/interface/src/types/graph-update.ts b/pkg/interface/src/types/graph-update.ts index cdcb1585e..2f452f908 100644 --- a/pkg/interface/src/types/graph-update.ts +++ b/pkg/interface/src/types/graph-update.ts @@ -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; export type Graphs = { [rid: string]: Graph }; - - diff --git a/pkg/interface/src/views/apps/links/components/comment-item.js b/pkg/interface/src/views/apps/links/components/comment-item.js index 1f37e045b..56a48acf7 100644 --- a/pkg/interface/src/views/apps/links/components/comment-item.js +++ b/pkg/interface/src/views/apps/links/components/comment-item.js @@ -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) => { - - - {content} - + + diff --git a/pkg/interface/src/views/apps/links/components/comment-submit.js b/pkg/interface/src/views/apps/links/components/comment-submit.js index baff2d4a8..65e26ea38 100644 --- a/pkg/interface/src/views/apps/links/components/comment-submit.js +++ b/pkg/interface/src/views/apps/links/components/comment-submit.js @@ -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( diff --git a/pkg/interface/src/views/apps/links/components/comments.js b/pkg/interface/src/views/apps/links/components/comments.js index ab1858a70..c8c56aeab 100644 --- a/pkg/interface/src/views/apps/links/components/comments.js +++ b/pkg/interface/src/views/apps/links/components/comments.js @@ -23,6 +23,7 @@ export const Comments = (props) => { return ( 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) { - {content} + ); diff --git a/pkg/interface/src/views/apps/publish/components/Comments.tsx b/pkg/interface/src/views/apps/publish/components/Comments.tsx index 9a2dcc1da..63bce78e2 100644 --- a/pkg/interface/src/views/apps/publish/components/Comments.tsx +++ b/pkg/interface/src/views/apps/publish/components/Comments.tsx @@ -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]) => ( + {_.map(content, (c, idx) => { + if ("text" in c) { + return ( + + {c.text} + + ); + } else if ("mention" in c) { + return ( + + ); + } + 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 ( + + {name} + + ); +} diff --git a/pkg/interface/src/views/components/RichText.js b/pkg/interface/src/views/components/RichText.js index 27b5a1d4f..b77347979 100644 --- a/pkg/interface/src/views/components/RichText.js +++ b/pkg/interface/src/views/components/RichText.js @@ -30,7 +30,7 @@ const RichText = React.memo(({ remoteContentPolicy, ...props }) => ( return {props.children}; }, paragraph: (paraProps) => { - return {paraProps.children}; + return {paraProps.children}; } }} plugins={[[