mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-09-21 15:38:59 +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",
|
"formik": "^2.1.5",
|
||||||
"immer": "^8.0.1",
|
"immer": "^8.0.1",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.20",
|
||||||
"markdown-to-jsx": "^6.11.4",
|
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"mousetrap": "^1.6.5",
|
"mousetrap": "^1.6.5",
|
||||||
"mousetrap-global-bind": "^1.1.0",
|
"mousetrap-global-bind": "^1.1.0",
|
||||||
@ -42,13 +41,15 @@
|
|||||||
"react-use-gesture": "^9.1.3",
|
"react-use-gesture": "^9.1.3",
|
||||||
"react-virtuoso": "^0.20.3",
|
"react-virtuoso": "^0.20.3",
|
||||||
"react-visibility-sensor": "^5.1.1",
|
"react-visibility-sensor": "^5.1.1",
|
||||||
|
"remark": "^12.0.0",
|
||||||
"remark-breaks": "^2.0.1",
|
"remark-breaks": "^2.0.1",
|
||||||
"remark-disable-tokenizers": "^1.0.24",
|
"remark-disable-tokenizers": "1.1.0",
|
||||||
"stacktrace-js": "^2.0.2",
|
"stacktrace-js": "^2.0.2",
|
||||||
"style-loader": "^1.3.0",
|
"style-loader": "^1.3.0",
|
||||||
"styled-components": "^5.1.1",
|
"styled-components": "^5.1.1",
|
||||||
"styled-system": "^5.1.5",
|
"styled-system": "^5.1.5",
|
||||||
"suncalc": "^1.8.0",
|
"suncalc": "^1.8.0",
|
||||||
|
"unist-util-visit": "^3.0.0",
|
||||||
"urbit-ob": "^5.0.1",
|
"urbit-ob": "^5.0.1",
|
||||||
"workbox-core": "^6.0.2",
|
"workbox-core": "^6.0.2",
|
||||||
"workbox-precaching": "^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 bigInt, { BigInteger } from 'big-integer';
|
||||||
import { buntPost } from '~/logic/lib/post';
|
import { buntPost } from '~/logic/lib/post';
|
||||||
import { unixToDa } from '~/logic/lib/util';
|
import { unixToDa } from '~/logic/lib/util';
|
||||||
|
//import tokenizeMessage from './tokenizeMessage';
|
||||||
|
|
||||||
export function newPost(
|
export function newPost(
|
||||||
title: string,
|
title: string,
|
||||||
@ -19,6 +20,9 @@ export function newPost(
|
|||||||
signatures: []
|
signatures: []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// re-enable on mainnet deploy
|
||||||
|
//const tokenisedBody = tokenizeMessage(body);
|
||||||
|
|
||||||
const revContainer: Post = { ...root, index: root.index + '/1' };
|
const revContainer: Post = { ...root, index: root.index + '/1' };
|
||||||
const commentsContainer = { ...root, index: root.index + '/2' };
|
const commentsContainer = { ...root, index: root.index + '/2' };
|
||||||
|
|
||||||
@ -26,6 +30,7 @@ export function newPost(
|
|||||||
...revContainer,
|
...revContainer,
|
||||||
index: revContainer.index + '/1',
|
index: revContainer.index + '/1',
|
||||||
contents: [{ text: title }, { text: body }]
|
contents: [{ text: title }, { text: body }]
|
||||||
|
//contents: [{ text: title }, { text: body } ...tokenisedBody]
|
||||||
};
|
};
|
||||||
|
|
||||||
const nodes = {
|
const nodes = {
|
||||||
@ -54,10 +59,13 @@ export function newPost(
|
|||||||
|
|
||||||
export function editPost(rev: number, noteId: BigInteger, title: string, body: string) {
|
export function editPost(rev: number, noteId: BigInteger, title: string, body: string) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
// reenable
|
||||||
|
//const tokenisedBody = tokenizeMessage(body);
|
||||||
const newRev: Post = {
|
const newRev: Post = {
|
||||||
author: `~${window.ship}`,
|
author: `~${window.ship}`,
|
||||||
index: `/${noteId.toString()}/1/${rev}`,
|
index: `/${noteId.toString()}/1/${rev}`,
|
||||||
'time-sent': now,
|
'time-sent': now,
|
||||||
|
//contents: [{ text: title }, ...tokenisedBody],
|
||||||
contents: [{ text: title }, { text: body }],
|
contents: [{ text: title }, { text: body }],
|
||||||
hash: null,
|
hash: null,
|
||||||
signatures: []
|
signatures: []
|
||||||
@ -85,8 +93,9 @@ export function getLatestRevision(node: GraphNode): [number, string, string, Pos
|
|||||||
if (!rev) {
|
if (!rev) {
|
||||||
return empty;
|
return empty;
|
||||||
}
|
}
|
||||||
const [title, body] = rev.post.contents as TextContent[];
|
const title = rev.post.contents[0];
|
||||||
return [revNum.toJSNumber(), title.text, body.text, rev.post];
|
const body = rev.post.contents.slice(1);
|
||||||
|
return [revNum.toJSNumber(), title.text, body, rev.post];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLatestCommentRevision(node: GraphNode): [number, Post] {
|
export function getLatestCommentRevision(node: GraphNode): [number, Post] {
|
||||||
@ -113,10 +122,11 @@ export function getComments(node: GraphNode): GraphNode {
|
|||||||
return comments;
|
return comments;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSnippet(body: string) {
|
export function getSnippet(body: any) {
|
||||||
const newlineIdx = body.indexOf('\n', 2);
|
const firstContent = Object.values(body[0])[0];
|
||||||
const end = newlineIdx > -1 ? newlineIdx : body.length;
|
const newlineIdx = firstContent.indexOf('\n', 2);
|
||||||
const start = body.substr(0, end);
|
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 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) => {
|
const isUrl = (string) => {
|
||||||
try {
|
try {
|
||||||
return URL_REGEX.test(string);
|
return URL_REGEX.test(string);
|
||||||
@ -15,17 +17,27 @@ const isRef = (str) => {
|
|||||||
return isUrl(str) && str.startsWith('web+urbitgraph://');
|
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 tokenizeMessage = (text) => {
|
||||||
const messages = [];
|
let messages = [];
|
||||||
let message = [];
|
// by line
|
||||||
|
let currTextBlock = [];
|
||||||
let isInCodeBlock = false;
|
let isInCodeBlock = false;
|
||||||
let endOfCodeBlock = false;
|
let endOfCodeBlock = false;
|
||||||
text.split(/\r?\n/).forEach((line, index) => {
|
text.split(/\r?\n/).forEach((line, index) => {
|
||||||
if (index !== 0) {
|
// by space
|
||||||
message.push('\n');
|
let currTextLine = [];
|
||||||
}
|
|
||||||
// A line of backticks enters and exits a codeblock
|
// 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
|
// But we need to check if we've ended a codeblock
|
||||||
endOfCodeBlock = isInCodeBlock;
|
endOfCodeBlock = isInCodeBlock;
|
||||||
isInCodeBlock = (!isInCodeBlock);
|
isInCodeBlock = (!isInCodeBlock);
|
||||||
@ -34,9 +46,13 @@ const tokenizeMessage = (text) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isInCodeBlock || endOfCodeBlock) {
|
if (isInCodeBlock || endOfCodeBlock) {
|
||||||
message.push(line);
|
currTextLine = [line];
|
||||||
} else {
|
} 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 (
|
if (
|
||||||
(str.startsWith('`') && str !== '`')
|
(str.startsWith('`') && str !== '`')
|
||||||
|| (str === '`' && !isInCodeBlock)
|
|| (str === '`' && !isInCodeBlock)
|
||||||
@ -50,9 +66,12 @@ const tokenizeMessage = (text) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(isRef(str) && !isInCodeBlock) {
|
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
|
// 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);
|
const link = parsePermalink(str);
|
||||||
if(!link) {
|
if(!link) {
|
||||||
@ -61,33 +80,39 @@ const tokenizeMessage = (text) => {
|
|||||||
const reference = permalinkToReference(link);
|
const reference = permalinkToReference(link);
|
||||||
messages.push(reference);
|
messages.push(reference);
|
||||||
}
|
}
|
||||||
message = [];
|
currTextLine = [];
|
||||||
} else if (isUrl(str) && !isInCodeBlock) {
|
} 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
|
// If we're in the middle of a message, add it to the stack and reset
|
||||||
messages.push({ text: message.join(' ') });
|
currTextLine.push('');
|
||||||
message = [];
|
messages.push({ text: currTextBlock.join('\n') + currTextLine.join(' ') });
|
||||||
|
currTextBlock = last ? [''] : [];
|
||||||
|
currTextLine = [];
|
||||||
}
|
}
|
||||||
messages.push({ url: str });
|
messages.push({ url: str });
|
||||||
message = [];
|
currTextLine = [];
|
||||||
} else if(urbitOb.isValidPatp(str) && !isInCodeBlock) {
|
} 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
|
// If we're in the middle of a message, add it to the stack and reset
|
||||||
messages.push({ text: message.join(' ') });
|
currTextLine.push('');
|
||||||
message = [];
|
messages.push({ text: currTextBlock.join('\n') + currTextLine.join(' ') });
|
||||||
|
currTextBlock = last ? [''] : [];
|
||||||
|
currTextLine = [];
|
||||||
}
|
}
|
||||||
messages.push({ mention: str });
|
messages.push({ mention: str });
|
||||||
message = [];
|
currTextLine = [];
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
message.push(str);
|
currTextLine.push(str);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
currTextBlock.push(currTextLine.join(' '))
|
||||||
});
|
});
|
||||||
|
|
||||||
if (message.length) {
|
if (currTextBlock.length) {
|
||||||
// Add any remaining message
|
// Add any remaining message
|
||||||
messages.push({ text: message.join(' ') });
|
messages.push({ text: currTextBlock.join('\n') });
|
||||||
}
|
}
|
||||||
return messages;
|
return messages;
|
||||||
};
|
};
|
||||||
|
@ -22,7 +22,8 @@ import useLocalState from '~/logic/state/local';
|
|||||||
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
|
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
|
||||||
import { Dropdown } from '~/views/components/Dropdown';
|
import { Dropdown } from '~/views/components/Dropdown';
|
||||||
import ProfileOverlay from '~/views/components/ProfileOverlay';
|
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';
|
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
|
||||||
|
|
||||||
@ -496,10 +497,10 @@ export const Message = React.memo(({
|
|||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
<GraphContentWide
|
<GraphContent
|
||||||
{...bind}
|
{...bind}
|
||||||
width="100%"
|
width="100%"
|
||||||
post={msg}
|
contents={msg.contents}
|
||||||
transcluded={transcluded}
|
transcluded={transcluded}
|
||||||
api={api}
|
api={api}
|
||||||
showOurContact={showOurContact}
|
showOurContact={showOurContact}
|
||||||
|
@ -19,7 +19,7 @@ import {
|
|||||||
GraphNotificationContents, GraphNotifIndex
|
GraphNotificationContents, GraphNotifIndex
|
||||||
} from '~/types';
|
} from '~/types';
|
||||||
import Author from '~/views/components/Author';
|
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 { PermalinkEmbed } from '../permalinks/embed';
|
||||||
import { Header } from './header';
|
import { Header } from './header';
|
||||||
|
|
||||||
@ -150,7 +150,7 @@ export const GraphNodeContent = ({ post, mod, index, hidden, association }) => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<TruncBox truncate={8}>
|
<TruncBox truncate={8}>
|
||||||
<GraphContentWide api={{} as any} post={post} showOurContact />
|
<GraphContent api={{} as any} contents={post.contents} showOurContact />
|
||||||
</TruncBox>
|
</TruncBox>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,7 @@ import { getSnippet } from '~/logic/lib/publish';
|
|||||||
import { useGroupForAssoc } from '~/logic/state/group';
|
import { useGroupForAssoc } from '~/logic/state/group';
|
||||||
import Author from '~/views/components/Author';
|
import Author from '~/views/components/Author';
|
||||||
import { MentionText } from '~/views/components/MentionText';
|
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 ChatMessage from '../chat/components/ChatMessage';
|
||||||
import { NotePreviewContent } from '../publish/components/NotePreview';
|
import { NotePreviewContent } from '../publish/components/NotePreview';
|
||||||
import { PermalinkEmbed } from './embed';
|
import { PermalinkEmbed } from './embed';
|
||||||
@ -72,10 +72,10 @@ function TranscludedComment(props: {
|
|||||||
group={group}
|
group={group}
|
||||||
/>
|
/>
|
||||||
<Box p="2">
|
<Box p="2">
|
||||||
<GraphContentWide
|
<GraphContent
|
||||||
api={api}
|
api={api}
|
||||||
transcluded={transcluded}
|
transcluded={transcluded}
|
||||||
post={comment.post}
|
contents={comment.post.contents}
|
||||||
showOurContact={false}
|
showOurContact={false}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@ -111,7 +111,7 @@ function TranscludedPublishNode(props: {
|
|||||||
</Text>
|
</Text>
|
||||||
<Box p="2">
|
<Box p="2">
|
||||||
<NotePreviewContent
|
<NotePreviewContent
|
||||||
snippet={getSnippet(post?.post.contents[1]?.text)}
|
snippet={getSnippet(post?.post.contents.slice(1))}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -6,6 +6,7 @@ import { RouteComponentProps, useLocation } from 'react-router-dom';
|
|||||||
import GlobalApi from '~/logic/api/global';
|
import GlobalApi from '~/logic/api/global';
|
||||||
import { editPost, getLatestRevision } from '~/logic/lib/publish';
|
import { editPost, getLatestRevision } from '~/logic/lib/publish';
|
||||||
import { useWaitForProps } from '~/logic/lib/useWaitForProps';
|
import { useWaitForProps } from '~/logic/lib/useWaitForProps';
|
||||||
|
import { referenceToPermalink } from '~/logic/lib/permalinks';
|
||||||
import { PostForm, PostFormSchema } from './NoteForm';
|
import { PostForm, PostFormSchema } from './NoteForm';
|
||||||
|
|
||||||
interface EditPostProps {
|
interface EditPostProps {
|
||||||
@ -21,10 +22,27 @@ export function EditPost(props: EditPostProps & RouteComponentProps): ReactEleme
|
|||||||
const [revNum, title, body] = getLatestRevision(note);
|
const [revNum, title, body] = getLatestRevision(note);
|
||||||
const location = useLocation();
|
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 waiter = useWaitForProps(props);
|
||||||
const initial: PostFormSchema = {
|
const initial: PostFormSchema = {
|
||||||
title,
|
title,
|
||||||
body
|
body: editContent
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async (
|
const onSubmit = async (
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { Action, Anchor, Box, Col, Row, Text } from '@tlon/indigo-react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Association, Graph, GraphNode, Group } from '@urbit/api';
|
import { Box, Text, Col, Anchor, Row, Action } from '@tlon/indigo-react';
|
||||||
import bigInt from 'big-integer';
|
import bigInt from 'big-integer';
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import ReactMarkdown from 'react-markdown';
|
|
||||||
import { Link, RouteComponentProps } from 'react-router-dom';
|
import { Link, RouteComponentProps } from 'react-router-dom';
|
||||||
import GlobalApi from '~/logic/api/global';
|
import GlobalApi from '~/logic/api/global';
|
||||||
import { roleForShip } from '~/logic/lib/group';
|
import { roleForShip } from '~/logic/lib/group';
|
||||||
|
import { Contacts, GraphNode, Graph, Association, Unreads, Group, Post } from '@urbit/api';
|
||||||
import { getPermalinkForGraph } from '~/logic/lib/permalinks';
|
import { getPermalinkForGraph } from '~/logic/lib/permalinks';
|
||||||
|
import { GraphContent } from '~/views/landscape/components/Graph/GraphContent';
|
||||||
import { getComments, getLatestRevision } from '~/logic/lib/publish';
|
import { getComments, getLatestRevision } from '~/logic/lib/publish';
|
||||||
import { useCopy } from '~/logic/lib/useCopy';
|
import { useCopy } from '~/logic/lib/useCopy';
|
||||||
import { useQuery } from '~/logic/lib/useQuery';
|
import { useQuery } from '~/logic/lib/useQuery';
|
||||||
@ -35,11 +35,10 @@ const renderers = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function NoteContent({ body }) {
|
export function NoteContent({ post, api }) {
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<Box color="black" className="md" style={{ overflowWrap: 'break-word', overflow: 'hidden' }}>
|
<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>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -124,7 +123,7 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
|||||||
</Author>
|
</Author>
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
<NoteContent body={body} />
|
<NoteContent api={props.api} post={post} />
|
||||||
<NoteNavigation
|
<NoteNavigation
|
||||||
notebook={notebook}
|
notebook={notebook}
|
||||||
noteId={noteId}
|
noteId={noteId}
|
||||||
|
@ -137,46 +137,6 @@
|
|||||||
display: none;
|
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) {
|
@media all and (prefers-color-scheme: dark) {
|
||||||
.options.open {
|
.options.open {
|
||||||
background-color: #4d4d4d;
|
background-color: #4d4d4d;
|
||||||
|
@ -11,8 +11,8 @@ import { getPermalinkForGraph } from '~/logic/lib/permalinks';
|
|||||||
import { getLatestCommentRevision } from '~/logic/lib/publish';
|
import { getLatestCommentRevision } from '~/logic/lib/publish';
|
||||||
import { useCopy } from '~/logic/lib/useCopy';
|
import { useCopy } from '~/logic/lib/useCopy';
|
||||||
import useMetadataState from '~/logic/state/metadata';
|
import useMetadataState from '~/logic/state/metadata';
|
||||||
|
import { GraphContent } from '../landscape/components/Graph/GraphContent';
|
||||||
import Author from '~/views/components/Author';
|
import Author from '~/views/components/Author';
|
||||||
import { GraphContentWide } from '../landscape/components/Graph/GraphContentWide';
|
|
||||||
|
|
||||||
const ClickBox = styled(Box)`
|
const ClickBox = styled(Box)`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -31,14 +31,13 @@ interface CommentItemProps {
|
|||||||
highlighted: boolean;
|
highlighted: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CommentItem(props: CommentItemProps): ReactElement {
|
export function CommentItem(props: CommentItemProps) {
|
||||||
let { highlighted } = props;
|
let { highlighted } = props;
|
||||||
const { ship, name, api, comment, group } = props;
|
const { ship, name, api, comment, group } = props;
|
||||||
const association = useMetadataState(
|
const association = useMetadataState(
|
||||||
useCallback(s => s.associations.graph[`/ship/${ship}/${name}`], [ship,name])
|
useCallback(s => s.associations.graph[`/ship/${ship}/${name}`], [ship,name])
|
||||||
);
|
);
|
||||||
const ref = useRef<HTMLElement | null>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
console.log(comment);
|
|
||||||
const [, post] = getLatestCommentRevision(comment);
|
const [, post] = getLatestCommentRevision(comment);
|
||||||
const disabled = props.pending;
|
const disabled = props.pending;
|
||||||
|
|
||||||
@ -131,14 +130,14 @@ export function CommentItem(props: CommentItemProps): ReactElement {
|
|||||||
</Row>
|
</Row>
|
||||||
</Author>
|
</Author>
|
||||||
</Row>
|
</Row>
|
||||||
<GraphContentWide
|
<GraphContent
|
||||||
borderRadius="1"
|
borderRadius="1"
|
||||||
p="1"
|
p="1"
|
||||||
mb="1"
|
mb="1"
|
||||||
backgroundColor={highlighted ? 'washedBlue' : 'white'}
|
backgroundColor={highlighted ? 'washedBlue' : 'white'}
|
||||||
transcluded={0}
|
transcluded={0}
|
||||||
api={api}
|
api={api}
|
||||||
post={post}
|
contents={post.contents}
|
||||||
showOurContact
|
showOurContact
|
||||||
/>
|
/>
|
||||||
</Box>
|
</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) => {
|
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 MessageMarkdown = React.memo((props) => {
|
||||||
const { source, ...rest } = props;
|
const { source, allowHeaders, allowLists, ...rest } = props;
|
||||||
const blockCode = source.split('```');
|
const blockCode = source.split('```');
|
||||||
const codeLines = blockCode.map(codes => codes.split('\n'));
|
const codeLines = blockCode.map((codes) => codes.split('\n'));
|
||||||
const lines = codeLines.reduce((acc, val, i) => {
|
let lines = [];
|
||||||
if (i % 2 === 1) {
|
if (allowLists) {
|
||||||
return [...acc, `\`\`\`${val.join('\n')}\`\`\``];
|
lines.push(source);
|
||||||
} else {
|
} else {
|
||||||
return [...acc, ...val];
|
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) => (
|
return lines.map((line, i) => (
|
||||||
<React.Fragment key={i}>
|
<React.Fragment key={i}>
|
||||||
@ -118,7 +136,7 @@ const MessageMarkdown = React.memo((props) => {
|
|||||||
[
|
[
|
||||||
RemarkDisableTokenizers,
|
RemarkDisableTokenizers,
|
||||||
{
|
{
|
||||||
block: DISABLED_BLOCK_TOKENS,
|
block: modifiedBlockTokens,
|
||||||
inline: DISABLED_INLINE_TOKENS
|
inline: DISABLED_INLINE_TOKENS
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -130,23 +148,26 @@ const MessageMarkdown = React.memo((props) => {
|
|||||||
|
|
||||||
export default function TextContent(props) {
|
export default function TextContent(props) {
|
||||||
const content = props.content;
|
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-])+([/-])?)+/
|
/([~][/])?(~[a-z]{3,6})(-[a-z]{6})?([/])(([a-z0-9-])+([/-])?)+/
|
||||||
);
|
);
|
||||||
const isGroupLink =
|
const isGroupLink =
|
||||||
group !== null && // matched possible chatroom
|
group !== null && // matched possible chatroom
|
||||||
group[2].length > 2 && // possible ship?
|
group[2].length > 2 && // possible ship?
|
||||||
urbitOb.isValidPatp(group[2]) && // valid patp?
|
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) {
|
if (isGroupLink) {
|
||||||
const resource = `/ship/${content.text}`;
|
const resource = `/ship/${content.text.trim()}`;
|
||||||
return (
|
return (
|
||||||
<GroupLink
|
<GroupLink
|
||||||
resource={resource}
|
resource={resource}
|
||||||
api={props.api}
|
api={props.api}
|
||||||
pl='2'
|
pl='2'
|
||||||
|
my='2'
|
||||||
border='1'
|
border='1'
|
||||||
borderRadius='2'
|
borderRadius='2'
|
||||||
borderColor='washedGray'
|
borderColor='washedGray'
|
||||||
@ -161,7 +182,7 @@ export default function TextContent(props) {
|
|||||||
lineHeight={props.lineHeight ? props.lineHeight : '20px'}
|
lineHeight={props.lineHeight ? props.lineHeight : '20px'}
|
||||||
style={{ overflowWrap: 'break-word' }}
|
style={{ overflowWrap: 'break-word' }}
|
||||||
>
|
>
|
||||||
<MessageMarkdown source={content.text} />
|
<MessageMarkdown source={content.text} allowHeaders={allowHeaders} allowLists={allowLists} />
|
||||||
</Text>
|
</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 React, { ReactElement } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import GlobalApi from '~/logic/api/global';
|
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)`
|
const TruncatedBox = styled(Col)`
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
@ -31,9 +31,9 @@ const PostContent = (props: PostContentProps): ReactElement => {
|
|||||||
textOverflow="ellipsis"
|
textOverflow="ellipsis"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
>
|
>
|
||||||
<GraphContentWide
|
<GraphContent
|
||||||
transcluded={0}
|
transcluded={0}
|
||||||
post={post}
|
contents={post.contents}
|
||||||
api={api}
|
api={api}
|
||||||
showOurContact
|
showOurContact
|
||||||
/>
|
/>
|
||||||
|
Loading…
Reference in New Issue
Block a user