mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-09-21 07:28:30 +03:00
commit
61e9deb787
BIN
pkg/interface/package-lock.json
generated
BIN
pkg/interface/package-lock.json
generated
Binary file not shown.
@ -24,7 +24,6 @@
|
||||
"formik": "^2.1.5",
|
||||
"immer": "^8.0.1",
|
||||
"lodash": "^4.17.20",
|
||||
"markdown-to-jsx": "^6.11.4",
|
||||
"moment": "^2.29.1",
|
||||
"mousetrap": "^1.6.5",
|
||||
"mousetrap-global-bind": "^1.1.0",
|
||||
@ -42,13 +41,15 @@
|
||||
"react-use-gesture": "^9.1.3",
|
||||
"react-virtuoso": "^0.20.3",
|
||||
"react-visibility-sensor": "^5.1.1",
|
||||
"remark": "^12.0.0",
|
||||
"remark-breaks": "^2.0.1",
|
||||
"remark-disable-tokenizers": "^1.0.24",
|
||||
"remark-disable-tokenizers": "1.1.0",
|
||||
"stacktrace-js": "^2.0.2",
|
||||
"style-loader": "^1.3.0",
|
||||
"styled-components": "^5.1.1",
|
||||
"styled-system": "^5.1.5",
|
||||
"suncalc": "^1.8.0",
|
||||
"unist-util-visit": "^3.0.0",
|
||||
"urbit-ob": "^5.0.1",
|
||||
"workbox-core": "^6.0.2",
|
||||
"workbox-precaching": "^6.0.2",
|
||||
|
@ -3,6 +3,7 @@ import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
import { buntPost } from '~/logic/lib/post';
|
||||
import { unixToDa } from '~/logic/lib/util';
|
||||
//import tokenizeMessage from './tokenizeMessage';
|
||||
|
||||
export function newPost(
|
||||
title: string,
|
||||
@ -19,6 +20,9 @@ export function newPost(
|
||||
signatures: []
|
||||
};
|
||||
|
||||
// re-enable on mainnet deploy
|
||||
//const tokenisedBody = tokenizeMessage(body);
|
||||
|
||||
const revContainer: Post = { ...root, index: root.index + '/1' };
|
||||
const commentsContainer = { ...root, index: root.index + '/2' };
|
||||
|
||||
@ -26,6 +30,7 @@ export function newPost(
|
||||
...revContainer,
|
||||
index: revContainer.index + '/1',
|
||||
contents: [{ text: title }, { text: body }]
|
||||
//contents: [{ text: title }, { text: body } ...tokenisedBody]
|
||||
};
|
||||
|
||||
const nodes = {
|
||||
@ -54,10 +59,13 @@ export function newPost(
|
||||
|
||||
export function editPost(rev: number, noteId: BigInteger, title: string, body: string) {
|
||||
const now = Date.now();
|
||||
// reenable
|
||||
//const tokenisedBody = tokenizeMessage(body);
|
||||
const newRev: Post = {
|
||||
author: `~${window.ship}`,
|
||||
index: `/${noteId.toString()}/1/${rev}`,
|
||||
'time-sent': now,
|
||||
//contents: [{ text: title }, ...tokenisedBody],
|
||||
contents: [{ text: title }, { text: body }],
|
||||
hash: null,
|
||||
signatures: []
|
||||
@ -85,8 +93,9 @@ export function getLatestRevision(node: GraphNode): [number, string, string, Pos
|
||||
if (!rev) {
|
||||
return empty;
|
||||
}
|
||||
const [title, body] = rev.post.contents as TextContent[];
|
||||
return [revNum.toJSNumber(), title.text, body.text, rev.post];
|
||||
const title = rev.post.contents[0];
|
||||
const body = rev.post.contents.slice(1);
|
||||
return [revNum.toJSNumber(), title.text, body, rev.post];
|
||||
}
|
||||
|
||||
export function getLatestCommentRevision(node: GraphNode): [number, Post] {
|
||||
@ -113,10 +122,11 @@ export function getComments(node: GraphNode): GraphNode {
|
||||
return comments;
|
||||
}
|
||||
|
||||
export function getSnippet(body: string) {
|
||||
const newlineIdx = body.indexOf('\n', 2);
|
||||
const end = newlineIdx > -1 ? newlineIdx : body.length;
|
||||
const start = body.substr(0, end);
|
||||
export function getSnippet(body: any) {
|
||||
const firstContent = Object.values(body[0])[0];
|
||||
const newlineIdx = firstContent.indexOf('\n', 2);
|
||||
const end = newlineIdx > -1 ? newlineIdx : firstContent.length;
|
||||
const start = firstContent.substr(0, end);
|
||||
|
||||
return (start === body || start.startsWith('![')) ? start : `${start}...`;
|
||||
return (start === firstContent || firstContent.startsWith('![')) ? start : `${start}...`;
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ import { parsePermalink, permalinkToReference } from '~/logic/lib/permalinks';
|
||||
|
||||
const URL_REGEX = new RegExp(String(/^(([\w\-\+]+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+\w)/.source));
|
||||
|
||||
const GROUP_REGEX = new RegExp(String(/^~[-a-z_]+\/[-a-z]+/.source));
|
||||
|
||||
const isUrl = (string) => {
|
||||
try {
|
||||
return URL_REGEX.test(string);
|
||||
@ -15,17 +17,27 @@ const isRef = (str) => {
|
||||
return isUrl(str) && str.startsWith('web+urbitgraph://');
|
||||
};
|
||||
|
||||
const isGroup = str => {
|
||||
try {
|
||||
return GROUP_REGEX.test(str);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const convertToGroupRef = (group) => `web+urbitgraph://group/${group}`;
|
||||
|
||||
const tokenizeMessage = (text) => {
|
||||
const messages = [];
|
||||
let message = [];
|
||||
let messages = [];
|
||||
// by line
|
||||
let currTextBlock = [];
|
||||
let isInCodeBlock = false;
|
||||
let endOfCodeBlock = false;
|
||||
text.split(/\r?\n/).forEach((line, index) => {
|
||||
if (index !== 0) {
|
||||
message.push('\n');
|
||||
}
|
||||
// by space
|
||||
let currTextLine = [];
|
||||
// A line of backticks enters and exits a codeblock
|
||||
if (line.startsWith('```')) {
|
||||
if (line.trim().startsWith('```')) {
|
||||
// But we need to check if we've ended a codeblock
|
||||
endOfCodeBlock = isInCodeBlock;
|
||||
isInCodeBlock = (!isInCodeBlock);
|
||||
@ -34,9 +46,13 @@ const tokenizeMessage = (text) => {
|
||||
}
|
||||
|
||||
if (isInCodeBlock || endOfCodeBlock) {
|
||||
message.push(line);
|
||||
currTextLine = [line];
|
||||
} else {
|
||||
line.split(/\s/).forEach((str) => {
|
||||
const words = line.split(/\s/);
|
||||
words.forEach((word, idx) => {
|
||||
const str = isGroup(word) ? convertToGroupRef(word) : word;
|
||||
|
||||
const last = words.length - 1 === idx;
|
||||
if (
|
||||
(str.startsWith('`') && str !== '`')
|
||||
|| (str === '`' && !isInCodeBlock)
|
||||
@ -50,9 +66,12 @@ const tokenizeMessage = (text) => {
|
||||
}
|
||||
|
||||
if(isRef(str) && !isInCodeBlock) {
|
||||
if (message.length > 0) {
|
||||
if (currTextLine.length > 0 || currTextBlock.length > 0) {
|
||||
// If we're in the middle of a message, add it to the stack and reset
|
||||
messages.push({ text: message.join(' ') });
|
||||
currTextLine.push('');
|
||||
messages.push({ text: currTextBlock.join('\n') + currTextLine.join(' ') });
|
||||
currTextBlock = last ? [''] : [];
|
||||
currTextLine = [];
|
||||
}
|
||||
const link = parsePermalink(str);
|
||||
if(!link) {
|
||||
@ -61,33 +80,39 @@ const tokenizeMessage = (text) => {
|
||||
const reference = permalinkToReference(link);
|
||||
messages.push(reference);
|
||||
}
|
||||
message = [];
|
||||
currTextLine = [];
|
||||
} else if (isUrl(str) && !isInCodeBlock) {
|
||||
if (message.length > 0) {
|
||||
if (currTextLine.length > 0 || currTextBlock.length > 0) {
|
||||
// If we're in the middle of a message, add it to the stack and reset
|
||||
messages.push({ text: message.join(' ') });
|
||||
message = [];
|
||||
currTextLine.push('');
|
||||
messages.push({ text: currTextBlock.join('\n') + currTextLine.join(' ') });
|
||||
currTextBlock = last ? [''] : [];
|
||||
currTextLine = [];
|
||||
}
|
||||
messages.push({ url: str });
|
||||
message = [];
|
||||
currTextLine = [];
|
||||
} else if(urbitOb.isValidPatp(str) && !isInCodeBlock) {
|
||||
if (message.length > 0) {
|
||||
if (currTextLine.length > 0 || currTextBlock.length > 0) {
|
||||
// If we're in the middle of a message, add it to the stack and reset
|
||||
messages.push({ text: message.join(' ') });
|
||||
message = [];
|
||||
currTextLine.push('');
|
||||
messages.push({ text: currTextBlock.join('\n') + currTextLine.join(' ') });
|
||||
currTextBlock = last ? [''] : [];
|
||||
currTextLine = [];
|
||||
}
|
||||
messages.push({ mention: str });
|
||||
message = [];
|
||||
currTextLine = [];
|
||||
|
||||
} else {
|
||||
message.push(str);
|
||||
currTextLine.push(str);
|
||||
}
|
||||
});
|
||||
}
|
||||
currTextBlock.push(currTextLine.join(' '))
|
||||
});
|
||||
|
||||
if (message.length) {
|
||||
if (currTextBlock.length) {
|
||||
// Add any remaining message
|
||||
messages.push({ text: message.join(' ') });
|
||||
messages.push({ text: currTextBlock.join('\n') });
|
||||
}
|
||||
return messages;
|
||||
};
|
||||
|
@ -22,7 +22,8 @@ import useLocalState from '~/logic/state/local';
|
||||
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
|
||||
import { Dropdown } from '~/views/components/Dropdown';
|
||||
import ProfileOverlay from '~/views/components/ProfileOverlay';
|
||||
import { GraphContentWide } from '~/views/landscape/components/Graph/GraphContentWide';
|
||||
import { GraphContent} from '~/views/landscape/components/Graph/GraphContent';
|
||||
|
||||
|
||||
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
|
||||
|
||||
@ -496,10 +497,10 @@ export const Message = React.memo(({
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<GraphContentWide
|
||||
<GraphContent
|
||||
{...bind}
|
||||
width="100%"
|
||||
post={msg}
|
||||
contents={msg.contents}
|
||||
transcluded={transcluded}
|
||||
api={api}
|
||||
showOurContact={showOurContact}
|
||||
|
@ -19,7 +19,7 @@ import {
|
||||
GraphNotificationContents, GraphNotifIndex
|
||||
} from '~/types';
|
||||
import Author from '~/views/components/Author';
|
||||
import { GraphContentWide } from '~/views/landscape/components/Graph/GraphContentWide';
|
||||
import { GraphContent } from '~/views/landscape/components/Graph/GraphContent';
|
||||
import { PermalinkEmbed } from '../permalinks/embed';
|
||||
import { Header } from './header';
|
||||
|
||||
@ -150,7 +150,7 @@ export const GraphNodeContent = ({ post, mod, index, hidden, association }) => {
|
||||
}
|
||||
return (
|
||||
<TruncBox truncate={8}>
|
||||
<GraphContentWide api={{} as any} post={post} showOurContact />
|
||||
<GraphContent api={{} as any} contents={post.contents} showOurContact />
|
||||
</TruncBox>
|
||||
);
|
||||
};
|
||||
|
@ -8,7 +8,7 @@ import { getSnippet } from '~/logic/lib/publish';
|
||||
import { useGroupForAssoc } from '~/logic/state/group';
|
||||
import Author from '~/views/components/Author';
|
||||
import { MentionText } from '~/views/components/MentionText';
|
||||
import { GraphContentWide } from '~/views/landscape/components/Graph/GraphContentWide';
|
||||
import { GraphContent } from '~/views/landscape/components/Graph/GraphContent';
|
||||
import ChatMessage from '../chat/components/ChatMessage';
|
||||
import { NotePreviewContent } from '../publish/components/NotePreview';
|
||||
import { PermalinkEmbed } from './embed';
|
||||
@ -72,10 +72,10 @@ function TranscludedComment(props: {
|
||||
group={group}
|
||||
/>
|
||||
<Box p="2">
|
||||
<GraphContentWide
|
||||
<GraphContent
|
||||
api={api}
|
||||
transcluded={transcluded}
|
||||
post={comment.post}
|
||||
contents={comment.post.contents}
|
||||
showOurContact={false}
|
||||
/>
|
||||
</Box>
|
||||
@ -111,7 +111,7 @@ function TranscludedPublishNode(props: {
|
||||
</Text>
|
||||
<Box p="2">
|
||||
<NotePreviewContent
|
||||
snippet={getSnippet(post?.post.contents[1]?.text)}
|
||||
snippet={getSnippet(post?.post.contents.slice(1))}
|
||||
/>
|
||||
</Box>
|
||||
</Col>
|
||||
|
@ -6,6 +6,7 @@ import { RouteComponentProps, useLocation } from 'react-router-dom';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { editPost, getLatestRevision } from '~/logic/lib/publish';
|
||||
import { useWaitForProps } from '~/logic/lib/useWaitForProps';
|
||||
import { referenceToPermalink } from '~/logic/lib/permalinks';
|
||||
import { PostForm, PostFormSchema } from './NoteForm';
|
||||
|
||||
interface EditPostProps {
|
||||
@ -21,10 +22,27 @@ export function EditPost(props: EditPostProps & RouteComponentProps): ReactEleme
|
||||
const [revNum, title, body] = getLatestRevision(note);
|
||||
const location = useLocation();
|
||||
|
||||
let editContent = null;
|
||||
editContent = body.reduce((val, curr) => {
|
||||
if ('text' in curr) {
|
||||
val = val + curr.text;
|
||||
} else if ('mention' in curr) {
|
||||
val = val + `~${curr.mention}`;
|
||||
} else if ('url' in curr) {
|
||||
val = val + curr.url;
|
||||
} else if ('code' in curr) {
|
||||
val = val + curr.code.expression;
|
||||
} else if ('reference' in curr) {
|
||||
val = `${val}${referenceToPermalink(curr).link}`;
|
||||
}
|
||||
|
||||
return val;
|
||||
}, '');
|
||||
|
||||
const waiter = useWaitForProps(props);
|
||||
const initial: PostFormSchema = {
|
||||
title,
|
||||
body
|
||||
body: editContent
|
||||
};
|
||||
|
||||
const onSubmit = async (
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Action, Anchor, Box, Col, Row, Text } from '@tlon/indigo-react';
|
||||
import { Association, Graph, GraphNode, Group } from '@urbit/api';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, Text, Col, Anchor, Row, Action } from '@tlon/indigo-react';
|
||||
import bigInt from 'big-integer';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { Link, RouteComponentProps } from 'react-router-dom';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { roleForShip } from '~/logic/lib/group';
|
||||
import { Contacts, GraphNode, Graph, Association, Unreads, Group, Post } from '@urbit/api';
|
||||
import { getPermalinkForGraph } from '~/logic/lib/permalinks';
|
||||
import { GraphContent } from '~/views/landscape/components/Graph/GraphContent';
|
||||
import { getComments, getLatestRevision } from '~/logic/lib/publish';
|
||||
import { useCopy } from '~/logic/lib/useCopy';
|
||||
import { useQuery } from '~/logic/lib/useQuery';
|
||||
@ -35,11 +35,10 @@ const renderers = {
|
||||
}
|
||||
};
|
||||
|
||||
export function NoteContent({ body }) {
|
||||
export function NoteContent({ post, api }) {
|
||||
return (
|
||||
|
||||
<Box color="black" className="md" style={{ overflowWrap: 'break-word', overflow: 'hidden' }}>
|
||||
<ReactMarkdown source={body} linkTarget={'_blank'} renderers={renderers} />
|
||||
<GraphContent tall contents={post.contents.slice(1)} showOurContact api={api} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@ -124,7 +123,7 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
</Author>
|
||||
</Row>
|
||||
</Col>
|
||||
<NoteContent body={body} />
|
||||
<NoteContent api={props.api} post={post} />
|
||||
<NoteNavigation
|
||||
notebook={notebook}
|
||||
noteId={noteId}
|
||||
|
@ -137,46 +137,6 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.md h1, .md h2, .md h3, .md h4, .md h5, .md p, .md a, .md ul, .md ol, .md blockquote,.md code,.md pre {
|
||||
font-size: 14px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.md ul ul {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.md h2, .md h3, .md h4, .md h5, .md p, .md a, .md ul {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.md h1 {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.md h2, .md h3, .md h4, .md h5 {
|
||||
color:var(--gray);
|
||||
}
|
||||
|
||||
.md {
|
||||
line-height: 1.5;
|
||||
}
|
||||
.md code, .md pre {
|
||||
font-family: "Source Code Pro", mono;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.md ul>li, .md ol>li {
|
||||
line-height: 1.5;
|
||||
}
|
||||
.md a {
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.md img {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
@media all and (prefers-color-scheme: dark) {
|
||||
.options.open {
|
||||
background-color: #4d4d4d;
|
||||
|
@ -11,8 +11,8 @@ import { getPermalinkForGraph } from '~/logic/lib/permalinks';
|
||||
import { getLatestCommentRevision } from '~/logic/lib/publish';
|
||||
import { useCopy } from '~/logic/lib/useCopy';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
import { GraphContent } from '../landscape/components/Graph/GraphContent';
|
||||
import Author from '~/views/components/Author';
|
||||
import { GraphContentWide } from '../landscape/components/Graph/GraphContentWide';
|
||||
|
||||
const ClickBox = styled(Box)`
|
||||
cursor: pointer;
|
||||
@ -31,14 +31,13 @@ interface CommentItemProps {
|
||||
highlighted: boolean;
|
||||
}
|
||||
|
||||
export function CommentItem(props: CommentItemProps): ReactElement {
|
||||
export function CommentItem(props: CommentItemProps) {
|
||||
let { highlighted } = props;
|
||||
const { ship, name, api, comment, group } = props;
|
||||
const association = useMetadataState(
|
||||
useCallback(s => s.associations.graph[`/ship/${ship}/${name}`], [ship,name])
|
||||
);
|
||||
const ref = useRef<HTMLElement | null>(null);
|
||||
console.log(comment);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [, post] = getLatestCommentRevision(comment);
|
||||
const disabled = props.pending;
|
||||
|
||||
@ -131,14 +130,14 @@ export function CommentItem(props: CommentItemProps): ReactElement {
|
||||
</Row>
|
||||
</Author>
|
||||
</Row>
|
||||
<GraphContentWide
|
||||
<GraphContent
|
||||
borderRadius="1"
|
||||
p="1"
|
||||
mb="1"
|
||||
backgroundColor={highlighted ? 'washedBlue' : 'white'}
|
||||
transcluded={0}
|
||||
api={api}
|
||||
post={post}
|
||||
contents={post.contents}
|
||||
showOurContact
|
||||
/>
|
||||
</Box>
|
||||
|
@ -0,0 +1,395 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import { Post, Content, ReferenceContent } from '@urbit/api';
|
||||
import {
|
||||
Box,
|
||||
Text,
|
||||
Anchor,
|
||||
H1,
|
||||
H2,
|
||||
H3,
|
||||
H4,
|
||||
Row,
|
||||
Col,
|
||||
} from '@tlon/indigo-react';
|
||||
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import TextContent from './content/text';
|
||||
import CodeContent from './content/code';
|
||||
import RemoteContent from '~/views/components/RemoteContent';
|
||||
import { Mention } from '~/views/components/MentionText';
|
||||
import { PermalinkEmbed } from '~/views/apps/permalinks/embed';
|
||||
import { referenceToPermalink } from '~/logic/lib/permalinks';
|
||||
import { GraphContentWide } from './GraphContentWide';
|
||||
import { PropFunc } from '~/types';
|
||||
import fromMarkdown from 'mdast-util-from-markdown';
|
||||
import Dot from '~/views/components/Dot';
|
||||
import {
|
||||
Root,
|
||||
Parent,
|
||||
Content as AstContent,
|
||||
BlockContent,
|
||||
} from '@types/mdast';
|
||||
import { parseTall, parseWide } from './parse';
|
||||
|
||||
type StitchMode = 'merge' | 'block' | 'inline';
|
||||
|
||||
// XX make better
|
||||
type GraphAstNode = any;
|
||||
|
||||
interface GraphMentionNode {
|
||||
type: 'graph-mention';
|
||||
ship: string;
|
||||
}
|
||||
interface GraphRefereceNode {
|
||||
type: 'graph-reference';
|
||||
reference: ReferenceContent;
|
||||
}
|
||||
|
||||
interface GraphUrl {
|
||||
type: 'graph-url';
|
||||
url: string;
|
||||
}
|
||||
const codeToMdAst = (content: CodeContent) => {
|
||||
return {
|
||||
type: 'root',
|
||||
children: [
|
||||
{
|
||||
type: 'code',
|
||||
value: content.code.expression
|
||||
},
|
||||
{
|
||||
type: 'code',
|
||||
value: (content.code.output || []).join('\n')
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
const contentToMdAst = (tall: boolean) => (
|
||||
content: Content
|
||||
): [StitchMode, any] => {
|
||||
if ('text' in content) {
|
||||
return ['merge', tall ? parseTall(content.text) : parseWide(content.text)] as [StitchMode, any];
|
||||
} else if ('code' in content) {
|
||||
return [
|
||||
'block',
|
||||
codeToMdAst(content)
|
||||
];
|
||||
} else if ('reference' in content) {
|
||||
return [
|
||||
'block',
|
||||
{
|
||||
type: 'root',
|
||||
children: [
|
||||
{
|
||||
type: 'graph-reference',
|
||||
reference: content.reference,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
} else if ('url' in content) {
|
||||
return [
|
||||
'block',
|
||||
{
|
||||
type: 'root',
|
||||
children: [
|
||||
{
|
||||
type: 'graph-url',
|
||||
url: content.url,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
} else if ('mention' in content) {
|
||||
return [
|
||||
'inline',
|
||||
{
|
||||
type: 'root',
|
||||
children: [
|
||||
{
|
||||
type: 'graph-mention',
|
||||
ship: content.mention,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
return [
|
||||
'inline',
|
||||
{
|
||||
type: 'root',
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
function stitchInline(a: any, b: any) {
|
||||
if (!a?.children) {
|
||||
throw new Error('Bad stitchInline call: missing root');
|
||||
}
|
||||
const lastParaIdx = a.children.length - 1;
|
||||
const last = a.children[lastParaIdx];
|
||||
if (last?.children) {
|
||||
const ros = {
|
||||
...a,
|
||||
children: [
|
||||
...a.children.slice(0, lastParaIdx),
|
||||
stitchInline(last, b),
|
||||
...a.children.slice(lastParaIdx + 1),
|
||||
],
|
||||
};
|
||||
return ros;
|
||||
}
|
||||
const res = { ...a, children: [...a.children, ...b] };
|
||||
return res;
|
||||
}
|
||||
|
||||
function last<T>(arr: T[]) {
|
||||
return arr[arr.length - 1];
|
||||
}
|
||||
|
||||
function getChildren<T extends {}>(node: T): AstContent[] {
|
||||
if ('children' in node) {
|
||||
// @ts-ignore
|
||||
return node.children;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function asParent<T extends BlockContent>(node: T): Parent | undefined {
|
||||
return ['paragraph', 'heading', 'list', 'listItem', 'table'].includes(
|
||||
node.type
|
||||
)
|
||||
? (node as Parent)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function stitchMerge(a: Root, b: Root) {
|
||||
const aChildren = a.children;
|
||||
const bChildren = b.children;
|
||||
if (last(aChildren)?.type === bChildren[0]?.type) {
|
||||
const aGrandchild = getChildren(last(aChildren));
|
||||
const bGrandchild = getChildren(bChildren[0]);
|
||||
const mergedPara = {
|
||||
...last(aChildren),
|
||||
children: [...aGrandchild, ...bGrandchild],
|
||||
};
|
||||
return {
|
||||
...a,
|
||||
children: [...aChildren.slice(0, -1), mergedPara, ...bChildren.slice(1)],
|
||||
};
|
||||
}
|
||||
return { ...a, children: [...aChildren, ...bChildren] };
|
||||
}
|
||||
|
||||
function stitchBlock(a: Root, b: AstContent[]) {
|
||||
return { ...a, children: [...a.children, ...b] };
|
||||
}
|
||||
|
||||
function stitchInlineAfterBlock(a: Root, b: GraphMentionNode[]) {
|
||||
return {
|
||||
...a,
|
||||
children: [...a.children, { type: 'paragraph', children: b }],
|
||||
};
|
||||
}
|
||||
|
||||
function stitchAsts(asts: [StitchMode, GraphAstNode][]) {
|
||||
return _.reduce(
|
||||
asts.slice(1),
|
||||
([prevMode, ast], [mode, val]): [StitchMode, GraphAstNode] => {
|
||||
if (prevMode === 'block') {
|
||||
if (mode === 'inline') {
|
||||
return [mode, stitchInlineAfterBlock(ast, val?.children ?? [])];
|
||||
}
|
||||
if (mode === 'merge') {
|
||||
return [mode, stitchBlock(ast, val?.children ?? [])];
|
||||
}
|
||||
if (mode === 'block') {
|
||||
return [mode, stitchBlock(ast, val?.children ?? [])];
|
||||
}
|
||||
}
|
||||
if (mode === 'inline') {
|
||||
return [mode, stitchInline(ast, val?.children ?? [])];
|
||||
}
|
||||
if (mode === 'merge') {
|
||||
return [mode, stitchMerge(ast, val)];
|
||||
}
|
||||
if (mode === 'block') {
|
||||
return [mode, stitchBlock(ast, val?.children ?? [])];
|
||||
}
|
||||
return [mode, ast];
|
||||
},
|
||||
asts[0]
|
||||
);
|
||||
}
|
||||
const header = ({ children, depth, ...rest }) => {
|
||||
const level = depth;
|
||||
const inner =
|
||||
level === 1 ? (
|
||||
<H1>{children}</H1>
|
||||
) : level === 2 ? (
|
||||
<H2>{children}</H2>
|
||||
) : level === 3 ? (
|
||||
<H3>{children}</H3>
|
||||
) : (
|
||||
<H4>{children}</H4>
|
||||
);
|
||||
return (
|
||||
<Box {...rest} mt="2" mb="4">
|
||||
{inner}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const renderers = {
|
||||
heading: header,
|
||||
inlineCode: ({ language, value }) => {
|
||||
return (
|
||||
<Text
|
||||
mono
|
||||
p="1"
|
||||
backgroundColor="washedGray"
|
||||
fontSize="0"
|
||||
style={{ whiteSpace: 'pre-wrap' }}
|
||||
>
|
||||
{value}
|
||||
</Text>
|
||||
);
|
||||
},
|
||||
|
||||
blockquote: ({ children, tall, ...rest }) => {
|
||||
return (
|
||||
<Text
|
||||
lineHeight="20px"
|
||||
display="block"
|
||||
borderLeft="1px solid"
|
||||
color="black"
|
||||
paddingLeft={2}
|
||||
py="1"
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
},
|
||||
paragraph: ({ children }) => {
|
||||
return (
|
||||
<Text fontSize="1" lineHeight={'20px'}>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
},
|
||||
listItem: ({ children }) => {
|
||||
return (
|
||||
<Box position="relative" alignItems="center">
|
||||
<Dot
|
||||
top="7px"
|
||||
position="absolute"
|
||||
left="0px"
|
||||
mr="1"
|
||||
height="20px"
|
||||
width="20px"
|
||||
/>
|
||||
<Box ml="2">{children}</Box>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
|
||||
code: ({ language, tall, value, ...rest }) => {
|
||||
console.log(rest);
|
||||
const inner = (
|
||||
<Text
|
||||
p="1"
|
||||
className="clamp-message"
|
||||
display="block"
|
||||
borderRadius="1"
|
||||
mono
|
||||
fontSize="0"
|
||||
backgroundColor="washedGray"
|
||||
overflowX="auto"
|
||||
style={{ whiteSpace: 'pre' }}
|
||||
>
|
||||
{value}
|
||||
</Text>
|
||||
);
|
||||
return tall ? <Box mb="2">{inner}</Box> : inner;
|
||||
},
|
||||
link: (props) => {
|
||||
return (
|
||||
<Anchor href={props.href} borderBottom="1" color="black">
|
||||
{props.children}
|
||||
</Anchor>
|
||||
);
|
||||
},
|
||||
list: ({ depth, children }) => {
|
||||
return (
|
||||
<Col ml="3" gapY="2" my="2">
|
||||
{children}
|
||||
</Col>
|
||||
);
|
||||
},
|
||||
'graph-mention': ({ ship }) => <Mention api={{} as any} ship={ship} />,
|
||||
'graph-url': ({ url }) => (
|
||||
<Box my="2" flexShrink={0}>
|
||||
<RemoteContent key={url} url={url} />
|
||||
</Box>
|
||||
),
|
||||
'graph-reference': ({ api, reference }) => {
|
||||
const { link } = referenceToPermalink({ reference });
|
||||
return (
|
||||
<PermalinkEmbed api={api} link={link} transcluded={0} showOurContact />
|
||||
);
|
||||
},
|
||||
root: ({ children }) => <Col gapY="2">{children}</Col>,
|
||||
text: ({ value }) => value,
|
||||
};
|
||||
|
||||
export function Graphdown<T extends {} = {}>(
|
||||
props: {
|
||||
ast: GraphAstNode;
|
||||
tall?: boolean;
|
||||
depth?: number;
|
||||
} & T
|
||||
) {
|
||||
const { ast, depth = 0, ...rest } = props;
|
||||
const { type, children = [], ...nodeRest } = ast;
|
||||
const Renderer = renderers[ast.type] ?? (() => `unknown element: ${type}`);
|
||||
|
||||
return (
|
||||
<Renderer depth={depth} {...rest} {...nodeRest}>
|
||||
{children.map((c) => (
|
||||
<Graphdown depth={depth+1} {...rest} ast={c} />
|
||||
))}
|
||||
</Renderer>
|
||||
);
|
||||
}
|
||||
|
||||
export const GraphContent = React.memo(function GraphContent(
|
||||
props: {
|
||||
tall?: boolean;
|
||||
transcluded?: number;
|
||||
contents: Content[];
|
||||
api: GlobalApi;
|
||||
showOurContact: boolean;
|
||||
} & PropFunc<typeof Box>
|
||||
) {
|
||||
const {
|
||||
post,
|
||||
contents,
|
||||
tall = false,
|
||||
transcluded = 0,
|
||||
showOurContact,
|
||||
api,
|
||||
...rest
|
||||
} = props;
|
||||
const [,ast] = stitchAsts(contents.map(contentToMdAst(tall)));
|
||||
return (
|
||||
<Box {...rest}>
|
||||
<Graphdown api={api} ast={ast} />
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
@ -1,81 +0,0 @@
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
import { Content, Post, ReferenceContent } from '@urbit/api';
|
||||
import React from 'react';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { referenceToPermalink } from '~/logic/lib/permalinks';
|
||||
import { PropFunc } from '~/types';
|
||||
import { PermalinkEmbed } from '~/views/apps/permalinks/embed';
|
||||
import { Mention } from '~/views/components/MentionText';
|
||||
import RemoteContent from '~/views/components/RemoteContent';
|
||||
import CodeContent from './content/code';
|
||||
import TextContent from './content/text';
|
||||
|
||||
function GraphContentWideInner(
|
||||
props: {
|
||||
transcluded?: number;
|
||||
post: Post;
|
||||
api: GlobalApi;
|
||||
showOurContact: boolean;
|
||||
} & PropFunc<typeof Box>
|
||||
) {
|
||||
const { post, transcluded = 0, showOurContact, api, ...rest } = props;
|
||||
|
||||
return (
|
||||
<Box {...rest}>
|
||||
{post.contents.map((content: Content, i) => {
|
||||
if ('text' in content) {
|
||||
return (
|
||||
<TextContent
|
||||
key={i}
|
||||
api={api}
|
||||
fontSize={1}
|
||||
lineHeight={'20px'}
|
||||
content={content}
|
||||
/>
|
||||
);
|
||||
} else if ('code' in content) {
|
||||
return <CodeContent key={i} content={content} />;
|
||||
} else if ('reference' in content) {
|
||||
const { link } = referenceToPermalink(content as ReferenceContent);
|
||||
return (
|
||||
<PermalinkEmbed
|
||||
link={link}
|
||||
api={api}
|
||||
transcluded={transcluded}
|
||||
showOurContact={showOurContact}
|
||||
/>
|
||||
);
|
||||
} else if ('url' in content) {
|
||||
return (
|
||||
<Box
|
||||
key={i}
|
||||
flexShrink={0}
|
||||
fontSize={1}
|
||||
lineHeight="20px"
|
||||
color="black"
|
||||
width="fit-content"
|
||||
maxWidth="min(500px, 100%)"
|
||||
>
|
||||
<RemoteContent
|
||||
key={content.url}
|
||||
// @ts-ignore Invalid prop detection due to double with() export
|
||||
url={content.url}
|
||||
transcluded={transcluded}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
} else if ('mention' in content) {
|
||||
const first = i => i === 0;
|
||||
return (<Mention
|
||||
key={i}
|
||||
first={first(i)}
|
||||
ship={content.mention}
|
||||
api={api}
|
||||
/>);
|
||||
}
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export const GraphContentWide = React.memo(GraphContentWideInner);
|
@ -75,21 +75,39 @@ const renderers = {
|
||||
);
|
||||
},
|
||||
link: (props) => {
|
||||
return <Anchor src={props.href} borderBottom="1" color="black">{props.children}</Anchor>;
|
||||
return <Anchor src={props.href} borderBottom="1" color="black">{props.children}</Anchor>
|
||||
},
|
||||
list: ({depth, children}) => {
|
||||
return <Text my='2' display='block' fontSize='1' ml={depth ? (2 * depth) : 0} lineHeight={'20px'}>{children}</Text>
|
||||
}
|
||||
};
|
||||
|
||||
const MessageMarkdown = React.memo((props) => {
|
||||
const { source, ...rest } = props;
|
||||
const { source, allowHeaders, allowLists, ...rest } = props;
|
||||
const blockCode = source.split('```');
|
||||
const codeLines = blockCode.map(codes => codes.split('\n'));
|
||||
const lines = codeLines.reduce((acc, val, i) => {
|
||||
if (i % 2 === 1) {
|
||||
return [...acc, `\`\`\`${val.join('\n')}\`\`\``];
|
||||
} else {
|
||||
return [...acc, ...val];
|
||||
const codeLines = blockCode.map((codes) => codes.split('\n'));
|
||||
let lines = [];
|
||||
if (allowLists) {
|
||||
lines.push(source);
|
||||
} else {
|
||||
lines = codeLines.reduce((acc, val, i) => {
|
||||
if (i % 2 === 1) {
|
||||
return [...acc, `\`\`\`${val.join('\n')}\`\`\``];
|
||||
} else {
|
||||
return [...acc, ...val];
|
||||
}
|
||||
}, []);
|
||||
}
|
||||
|
||||
const modifiedBlockTokens = DISABLED_BLOCK_TOKENS.filter(e => {
|
||||
if (allowHeaders && allowLists) {
|
||||
return (e in ["setextHeading", "atxHeading", "list"])
|
||||
} else if (allowHeaders) {
|
||||
return (e in ["setextHeading", "atxHeading"])
|
||||
} else if (allowLists) {
|
||||
return (e === "list")
|
||||
}
|
||||
}, []);
|
||||
})
|
||||
|
||||
return lines.map((line, i) => (
|
||||
<React.Fragment key={i}>
|
||||
@ -118,7 +136,7 @@ const MessageMarkdown = React.memo((props) => {
|
||||
[
|
||||
RemarkDisableTokenizers,
|
||||
{
|
||||
block: DISABLED_BLOCK_TOKENS,
|
||||
block: modifiedBlockTokens,
|
||||
inline: DISABLED_INLINE_TOKENS
|
||||
}
|
||||
]
|
||||
@ -130,23 +148,26 @@ const MessageMarkdown = React.memo((props) => {
|
||||
|
||||
export default function TextContent(props) {
|
||||
const content = props.content;
|
||||
const allowHeaders = props.allowHeaders;
|
||||
const allowLists = props.allowLists;
|
||||
|
||||
const group = content.text.match(
|
||||
const group = content.text.trim().match(
|
||||
/([~][/])?(~[a-z]{3,6})(-[a-z]{6})?([/])(([a-z0-9-])+([/-])?)+/
|
||||
);
|
||||
const isGroupLink =
|
||||
group !== null && // matched possible chatroom
|
||||
group[2].length > 2 && // possible ship?
|
||||
urbitOb.isValidPatp(group[2]) && // valid patp?
|
||||
group[0] === content.text; // entire message is room name?
|
||||
group[0] === content.text.trim(); // entire message is room name?
|
||||
|
||||
if (isGroupLink) {
|
||||
const resource = `/ship/${content.text}`;
|
||||
const resource = `/ship/${content.text.trim()}`;
|
||||
return (
|
||||
<GroupLink
|
||||
resource={resource}
|
||||
api={props.api}
|
||||
pl='2'
|
||||
my='2'
|
||||
border='1'
|
||||
borderRadius='2'
|
||||
borderColor='washedGray'
|
||||
@ -161,7 +182,7 @@ export default function TextContent(props) {
|
||||
lineHeight={props.lineHeight ? props.lineHeight : '20px'}
|
||||
style={{ overflowWrap: 'break-word' }}
|
||||
>
|
||||
<MessageMarkdown source={content.text} />
|
||||
<MessageMarkdown source={content.text} allowHeaders={allowHeaders} allowLists={allowLists} />
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
33
pkg/interface/src/views/landscape/components/Graph/parse.ts
Normal file
33
pkg/interface/src/views/landscape/components/Graph/parse.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import remark from 'remark';
|
||||
import RemarkDisableTokenizers from 'remark-disable-tokenizers';
|
||||
|
||||
const DISABLED_BLOCK_TOKENS = [
|
||||
'indentedCode',
|
||||
'atxHeading',
|
||||
'thematicBreak',
|
||||
'list',
|
||||
'setextHeading',
|
||||
'html',
|
||||
'definition',
|
||||
'table',
|
||||
];
|
||||
|
||||
const DISABLED_INLINE_TOKENS = ['autoLink', 'url', 'email', 'reference'];
|
||||
|
||||
const tallParser = remark().freeze();
|
||||
|
||||
export const parseTall = (text: string) => tallParser.parse(text);
|
||||
|
||||
const wideParser = remark()
|
||||
.use([
|
||||
[
|
||||
RemarkDisableTokenizers,
|
||||
{
|
||||
block: DISABLED_BLOCK_TOKENS,
|
||||
inline: DISABLED_INLINE_TOKENS,
|
||||
},
|
||||
],
|
||||
])
|
||||
.freeze();
|
||||
|
||||
export const parseWide = (text: string) => wideParser.parse(text);
|
@ -3,7 +3,7 @@ import { Post } from '@urbit/api';
|
||||
import React, { ReactElement } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { GraphContentWide } from '~/views/landscape/components/Graph/GraphContentWide';
|
||||
import { GraphContent } from '~/views/landscape/components/Graph/GraphContent';
|
||||
|
||||
const TruncatedBox = styled(Col)`
|
||||
display: -webkit-box;
|
||||
@ -31,9 +31,9 @@ const PostContent = (props: PostContentProps): ReactElement => {
|
||||
textOverflow="ellipsis"
|
||||
overflow="hidden"
|
||||
>
|
||||
<GraphContentWide
|
||||
<GraphContent
|
||||
transcluded={0}
|
||||
post={post}
|
||||
contents={post.contents}
|
||||
api={api}
|
||||
showOurContact
|
||||
/>
|
||||
|
Loading…
Reference in New Issue
Block a user