Merge branch 'release/next-js' into lf/more-virt-perf

This commit is contained in:
Matilde Park 2021-04-27 15:54:27 -04:00
commit 370b7ad9de
29 changed files with 562 additions and 187 deletions

View File

@ -25,10 +25,10 @@ module Urbit.Arvo.Common
import Urbit.Prelude
import Control.Monad.Fail (fail)
import Data.Bits
import Data.Serialize
import qualified Network.HTTP.Types.Method as H
import qualified Network.Socket as N
import qualified Urbit.Ob as Ob
@ -159,6 +159,19 @@ deriveNoun ''JsonNode
-- Ames Destinations -------------------------------------------------
serializeToNoun :: Serialize a => a -> Noun
serializeToNoun = A . bytesAtom . encode
serializeParseNoun :: Serialize a => String -> Int -> Noun -> Parser a
serializeParseNoun desc len = named (pack desc) . \case
A (atomBytes -> bs)
-- Atoms lose leading 0s, but since lsb, these become trailing NULs
| length bs <= len -> case decode $ bs <> replicate (len - length bs) 0 of
Right aa -> pure aa
Left msg -> fail msg
| otherwise -> fail ("putative " <> desc <> " " <> show bs <> " too long")
C{} -> fail ("unexpected cell in " <> desc)
newtype Patp a = Patp { unPatp :: a }
deriving newtype (Eq, Ord, Enum, Real, Integral, Num, ToNoun, FromNoun)
@ -167,17 +180,29 @@ newtype Port = Port { unPort :: Word16 }
deriving newtype (Eq, Ord, Show, Enum, Real, Integral, Num, ToNoun, FromNoun)
-- @if
newtype Ipv4 = Ipv4 { unIpv4 :: Word32 }
deriving newtype (Eq, Ord, Enum, Real, Integral, Num, ToNoun, FromNoun)
newtype Ipv4 = Ipv4 { unIpv4 :: N.HostAddress }
deriving newtype (Eq, Ord, Enum)
instance Serialize Ipv4 where
get = (\a b c d -> Ipv4 $ N.tupleToHostAddress $ (d, c, b, a))
<$> getWord8 <*> getWord8 <*> getWord8 <*> getWord8
put (Ipv4 (N.hostAddressToTuple -> (a, b, c, d))) = for_ [d, c, b, a] putWord8
instance ToNoun Ipv4 where
toNoun = serializeToNoun
instance FromNoun Ipv4 where
parseNoun = serializeParseNoun "Ipv4" 4
instance Show Ipv4 where
show (Ipv4 i) =
show ((shiftR i 24) .&. 0xff) ++ "." ++
show ((shiftR i 16) .&. 0xff) ++ "." ++
show ((shiftR i 8) .&. 0xff) ++ "." ++
show (i .&. 0xff)
show (Ipv4 (N.hostAddressToTuple -> (a, b, c, d))) =
show a ++ "." ++
show b ++ "." ++
show c ++ "." ++
show d
-- @is
-- should probably use hostAddress6ToTuple here, but no one uses it right now
newtype Ipv6 = Ipv6 { unIpv6 :: Word128 }
deriving newtype (Eq, Ord, Show, Enum, Real, Integral, Num, ToNoun, FromNoun)
@ -190,21 +215,14 @@ data AmesAddress = AAIpv4 Ipv4 Port
deriving (Eq, Ord, Show)
instance Serialize AmesAddress where
get = AAIpv4 <$> (Ipv4 <$> getWord32le) <*> (Port <$> getWord16le)
put (AAIpv4 (Ipv4 ip) (Port port)) = putWord32le ip >> putWord16le port
get = AAIpv4 <$> get <*> (Port <$> getWord16le)
put (AAIpv4 ip (Port port)) = put ip >> putWord16le port
instance FromNoun AmesAddress where
parseNoun = named "AmesAddress" . \case
A (atomBytes -> bs)
-- Atoms lose leading 0s, but since lsb, these become trailing NULs
| length bs <= 6 -> case decode $ bs <> replicate (6 - length bs) 0 of
Right aa -> pure aa
Left msg -> fail msg
| otherwise -> fail ("putative address " <> show bs <> " too long")
C{} -> fail "unexpected cell in ames address"
parseNoun = serializeParseNoun "AmesAddress" 6
instance ToNoun AmesAddress where
toNoun = A . bytesAtom . encode
toNoun = serializeToNoun
type AmesDest = Each Galaxy AmesAddress

View File

@ -80,10 +80,6 @@ data ShipClass
muk :: ByteString -> Word20
muk bs = mugBS bs .&. (2 ^ 20 - 1)
-- XX check this
getAmesAddress :: Get AmesAddress
getAmesAddress = AAIpv4 <$> (Ipv4 <$> getWord32le) <*> (Port <$> getWord16le)
putAmesAddress :: Putter AmesAddress
putAmesAddress = \case
AAIpv4 (Ipv4 ip) (Port port) -> putWord32le ip >> putWord16le port
@ -104,7 +100,7 @@ instance Serialize Packet where
guard isAmes
pktOrigin <- if isRelayed
then Just <$> getAmesAddress
then Just <$> get
else pure Nothing
-- body
@ -157,9 +153,10 @@ instance Serialize Packet where
putWord32le head
case pktOrigin of
Just o -> putAmesAddress o
Just o -> put o
Nothing -> pure ()
putByteString body
where
putShipGetRank s@(Ship (LargeKey p q)) = case () of
_ | s < 2 ^ 16 -> (0, putWord16le $ fromIntegral s) -- lord

View File

@ -4,8 +4,12 @@
1. Opens a UDP socket and makes sure that it stays open.
- If can't open the port, wait and try again repeatedly.
- If there is an error reading or writting from the open socket,
close it and open another.
- If there is an error reading to or writing from the open socket,
close it and open another, making sure, however, to reuse the
same port
NOTE: It's not clear what, if anything, closing and reopening
the socket does. We're keeping this behavior out of conservatism
until we understand it better.
2. Receives packets from the socket.
@ -158,7 +162,7 @@ realUdpServ
-> HostAddress
-> AmesStat
-> RIO e UdpServ
realUdpServ por hos sat = do
realUdpServ startPort hos sat = do
logInfo $ displayShow ("AMES", "UDP", "Starting real UDP server.")
env <- ask
@ -202,23 +206,30 @@ realUdpServ por hos sat = do
did <- atomically (tryWriteTBQueue qSend (a, b))
when (did == False) $ do
logWarn "AMES: UDP: Dropping outbound packet because queue is full."
let opener por = do
logInfo $ displayShow $ ("AMES", "UDP", "Trying to open socket, port",)
por
sk <- forceBind por hos
sn <- io $ getSocketName sk
sp <- io $ socketPort sk
logInfo $ displayShow $ ("AMES", "UDP", "Got socket", sn, sp)
tOpen <- async $ forever $ do
sk <- forceBind por hos
sn <- io $ getSocketName sk
let waitForRelease = do
atomically (writeTVar vSock (Just sk))
broken <- atomically (takeTMVar vFail)
logWarn "AMES: UDP: Closing broken socket."
io (close broken)
let waitForRelease = do
atomically (writeTVar vSock (Just sk))
broken <- atomically (takeTMVar vFail)
logWarn "AMES: UDP: Closing broken socket."
io (close broken)
case sn of
(SockAddrInet boundPort _) ->
-- When we're on IPv4, maybe port forward at the NAT.
rwith (requestPortAccess $ fromIntegral boundPort) $
\() -> waitForRelease
_ -> waitForRelease
case sn of
(SockAddrInet boundPort _) ->
-- When we're on IPv4, maybe port forward at the NAT.
rwith (requestPortAccess $ fromIntegral boundPort) $
\() -> waitForRelease
_ -> waitForRelease
opener sp
tOpen <- async $ opener startPort
tSend <- async $ forever $ join $ atomically $ do
(adr, byt) <- readTBQueue qSend

View File

@ -37,12 +37,12 @@ textPlain = Path [(MkKnot "text"), (MkKnot "plain")]
-- | Filter for dotfiles, tempfiles and backup files.
validClaySyncPath :: FilePath -> Bool
validClaySyncPath fp = hasPeriod && notTildeFile && notDotHash && notDoubleHash
validClaySyncPath fp = hasPeriod && notTildeFile && notDotFile && notDoubleHash
where
fileName = takeFileName fp
hasPeriod = elem '.' fileName
notTildeFile = not $ "~" `isSuffixOf` fileName
notDotHash = not $ ".#" `isPrefixOf` fileName
notDotFile = not $ "." `isPrefixOf` fileName
notDoubleHash =
not $ ("#" `isPrefixOf` fileName) && ("#" `isSuffixOf` fileName)

View File

@ -1,5 +1,5 @@
name: urbit-king
version: 1.3
version: 1.4
license: MIT
license-file: LICENSE
data-files:

View File

@ -12,6 +12,7 @@ import { Contacts, Content } from '@urbit/api';
import { Row, BaseImage, Box, Icon, LoadingSpinner } from '@tlon/indigo-react';
import withStorage from '~/views/components/withStorage';
import { withLocalState } from '~/logic/state/local';
import { MOBILE_BROWSER_REGEX } from "~/logic/lib/util";
type ChatInputProps = IuseStorage & {
api: GlobalApi;
@ -30,6 +31,7 @@ interface ChatInputState {
inCodeMode: boolean;
submitFocus: boolean;
uploadingPaste: boolean;
currentInput: string;
}
class ChatInput extends Component<ChatInputProps, ChatInputState> {
@ -41,7 +43,8 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
this.state = {
inCodeMode: false,
submitFocus: false,
uploadingPaste: false
uploadingPaste: false,
currentInput: props.message
};
this.chatEditor = React.createRef();
@ -50,6 +53,7 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
this.toggleCode = this.toggleCode.bind(this);
this.uploadSuccess = this.uploadSuccess.bind(this);
this.uploadError = this.uploadError.bind(this);
this.eventHandler = this.eventHandler.bind(this);
}
toggleCode() {
@ -61,6 +65,7 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
submit(text) {
const { props, state } = this;
const [, , ship, name] = props.station.split('/');
this.setState({ currentInput: '' });
if (state.inCodeMode) {
this.setState(
{
@ -120,6 +125,10 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
});
}
eventHandler(value) {
this.setState({ currentInput: value });
}
render() {
const { props, state } = this;
@ -130,6 +139,7 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
const avatar =
props.ourContact && props.ourContact?.avatar && !props.hideAvatars ? (
<BaseImage
flexShrink={0}
src={props.ourContact.avatar}
height={24}
width={24}
@ -170,7 +180,7 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
className='cf'
zIndex={0}
>
<Row p='12px 4px 12px 12px' alignItems='center'>
<Row p='12px 4px 12px 12px' flexShrink={0} alignItems='center'>
{avatar}
</Row>
<ChatEditor
@ -180,15 +190,25 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
onUnmount={props.onUnmount}
message={props.message}
onPaste={this.onPaste.bind(this)}
changeEvent={this.eventHandler}
placeholder='Message...'
/>
<Box mx={2} flexShrink={0} height='16px' width='16px' flexBasis='16px'>
<Box mx='12px' flexShrink={0} height='16px' width='16px' flexBasis='16px'>
<Icon
icon='Dojo'
cursor='pointer'
onClick={this.toggleCode}
color={state.inCodeMode ? 'blue' : 'black'}
/>
</Box>
<Box ml='12px' mr={3} flexShrink={0} height='16px' width='16px' flexBasis='16px'>
{this.props.canUpload ? (
this.props.uploading ? (
<LoadingSpinner />
) : (
<Icon
icon='Attachment'
cursor='pointer'
width='16'
height='16'
onClick={() =>
@ -198,13 +218,24 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
)
) : null}
</Box>
<Box mr={2} flexShrink={0} height='16px' width='16px' flexBasis='16px'>
<Icon
icon='Dojo'
onClick={this.toggleCode}
color={state.inCodeMode ? 'blue' : 'black'}
/>
</Box>
{MOBILE_BROWSER_REGEX.test(navigator.userAgent) ?
<Box
ml={2}
mr="12px"
flexShrink={0}
display="flex"
justifyContent="center"
alignItems="center"
width="24px"
height="24px"
borderRadius="50%"
backgroundColor={state.currentInput !== '' ? 'blue' : 'gray'}
cursor={state.currentInput !== '' ? 'pointer' : 'default'}
onClick={() => this.chatEditor.current.submit()}
>
<Icon icon="ArrowEast" color="white" />
</Box>
: null}
</Row>
);
}

View File

@ -255,6 +255,7 @@ interface ChatMessageProps {
}
function ChatMessage(props: ChatMessageProps) {
let { highlighted } = this.props;
const {
msg,
previousMsg,
@ -268,7 +269,6 @@ function ChatMessage(props: ChatMessageProps) {
isLastMessage,
unreadMarkerRef,
api,
highlighted,
showOurContact,
fontSize,
hideHover
@ -281,7 +281,15 @@ function ChatMessage(props: ChatMessageProps) {
msg.number === 1
);
const ourMention = msg?.contents?.some((e) => {
return e?.mention && e?.mention === window.ship;
});
if (!highlighted) {
if (ourMention) {
highlighted = true;
}
}
const date = useMemo(() => daToUnix(bigInt(msg.index.split('/')[1])), [msg.index]);
const nextDate = useMemo(() => nextMsg ? (
@ -290,7 +298,7 @@ function ChatMessage(props: ChatMessageProps) {
[nextMsg]
);
const dayBreak = useMemo(() =>
const dayBreak = useMemo(() =>
nextDate &&
new Date(date).getDate() !==
new Date(nextDate).getDate()
@ -572,7 +580,7 @@ export const MessagePlaceholder = ({
>
<Text
display='block'
background='gray'
background='washedGray'
width='24px'
height='24px'
borderRadius='50%'
@ -595,12 +603,13 @@ export const MessagePlaceholder = ({
display='inline-block'
verticalAlign='middle'
fontSize='0'
gray
washedGray
cursor='default'
>
<Text maxWidth='32rem' display='block'>
<Text
backgroundColor='gray'
backgroundColor='washedGray'
borderRadius='2'
display='block'
width='100%'
height='100%'
@ -612,10 +621,11 @@ export const MessagePlaceholder = ({
mono
verticalAlign='middle'
fontSize='0'
gray
washedGray
>
<Text
background='gray'
background='washedGray'
borderRadius='2'
display='block'
height='1em'
style={{ width: `${((index % 3) + 1) * 3}em` }}
@ -626,12 +636,14 @@ export const MessagePlaceholder = ({
verticalAlign='middle'
fontSize='0'
ml='2'
gray
washedGray
borderRadius='2'
display={['none', 'inline-block']}
className='child'
>
<Text
backgroundColor='gray'
backgroundColor='washedGray'
borderRadius='2'
display='block'
width='100%'
height='100%'
@ -640,7 +652,8 @@ export const MessagePlaceholder = ({
</Box>
<Text
display='block'
backgroundColor='gray'
backgroundColor='washedGray'
borderRadius='2'
height='1em'
style={{ width: `${(index % 5) * 20}%` }}
></Text>

View File

@ -162,6 +162,7 @@ export default class ChatEditor extends Component {
editor.showHint(['test', 'foo']);
}
if (this.state.message !== '' && value == '') {
this.props.changeEvent(value);
this.setState({
message: value
});
@ -169,6 +170,7 @@ export default class ChatEditor extends Component {
if (value == this.props.message || value == '' || value == ' ') {
return;
}
this.props.changeEvent(value);
this.setState({
message: value
});

View File

@ -19,7 +19,7 @@ interface LinkItemProps {
node: GraphNode;
association: Association;
resource: string; api: GlobalApi; group: Group; path: string; }
export const LinkItem = React.forwardRef((props: LinkItemProps, ref): ReactElement => {
export const LinkItem = React.forwardRef((props: LinkItemProps, ref): ReactElement => {
const {
association,
node,
@ -85,7 +85,7 @@ export const LinkItem = React.forwardRef((props: LinkItemProps, ref): ReactEleme
permalink,
'Copy reference'
);
const deleteLink = () => {
if (confirm('Are you sure you want to delete this link?')) {
api.graph.removeNodes(`~${ship}`, name, [node.post.index]);
@ -96,7 +96,7 @@ export const LinkItem = React.forwardRef((props: LinkItemProps, ref): ReactEleme
const unreads = useHarkState(state => state.unreads);
const commColor = (unreads.graph?.[appPath]?.[`/${index}`]?.unreads ?? 0) > 0 ? 'blue' : 'gray';
const isUnread = unreads.graph?.[appPath]?.['/']?.unreads?.has(node.post.index);
return (
<Box
mx="auto"
@ -166,9 +166,11 @@ export const LinkItem = React.forwardRef((props: LinkItemProps, ref): ReactEleme
<Row minWidth='0' flexShrink={0} width="100%" justifyContent="space-between" py={3} bg="white">
<Author
showImage
isRelativeTime
ship={author}
date={node.post['time-sent']}
group={group}
lineHeight="1"
/>
<Box ml="auto">
<Link

View File

@ -115,11 +115,12 @@ export function Note(props: NoteProps & RouteComponentProps) {
<Row alignItems="center">
<Author
showImage
isRelativeTime
ship={post?.author}
date={post?.['time-sent']}
group={group}
>
<Row px="2" gapX="2" alignItems="flex-end">
<Row px="2" gapX="2" alignItems="flex-end" height="14px">
<Action bg="white" onClick={doCopy}>{copyDisplay}</Action>
{adminLinks}
</Row>

View File

@ -24,6 +24,7 @@ interface AuthorProps {
unread?: boolean;
api?: GlobalApi;
size?: number;
lineHeight?: string;
}
// eslint-disable-next-line max-lines-per-function
@ -38,10 +39,11 @@ export default function Author(props: AuthorProps & PropFunc<typeof Box>): React
group,
isRelativeTime,
dontShowTime,
lineHeight = 'tall',
...rest
} = props;
const time = props.time || false;
const time = props.time || props.date || false;
const size = props.size || 16;
const sigilPadding = props.sigilPadding || 2;
@ -89,7 +91,7 @@ export default function Author(props: AuthorProps & PropFunc<typeof Box>): React
) : sigil;
return (
<Row height="20px" {...rest} alignItems='center' width='auto'>
<Row {...rest} alignItems='center' width='auto'>
<Box
onClick={(e) => {
e.stopPropagation();
@ -110,7 +112,7 @@ export default function Author(props: AuthorProps & PropFunc<typeof Box>): React
color='black'
fontSize='1'
cursor='pointer'
lineHeight='tall'
lineHeight={lineHeight}
fontFamily={showNickname ? 'sans' : 'mono'}
fontWeight={showNickname ? '500' : '400'}
mr={showNickname ? 0 : "2px"}
@ -121,6 +123,7 @@ export default function Author(props: AuthorProps & PropFunc<typeof Box>): React
</Box>
{ !dontShowTime && time && (
<Timestamp
height="fit-content"
relative={isRelativeTime}
stamp={stamp}
fontSize={1}

View File

@ -73,7 +73,7 @@ export function ColorInput(props: ColorInputProps) {
height='100%'
alignSelf='stretch'
onChange={onChange}
value={`#${padded}`}
value={padded}
disabled={disabled || false}
type='color'
opacity={0}

View File

@ -35,6 +35,7 @@ interface CommentItemProps {
}
export function CommentItem(props: CommentItemProps): ReactElement {
let { highlighted } = props;
const { ship, name, api, comment, group } = props;
const association = useMetadataState(
useCallback(s => s.associations.graph[`/ship/${ship}/${name}`], [ship,name])
@ -47,6 +48,16 @@ export function CommentItem(props: CommentItemProps): ReactElement {
await api.graph.removeNodes(ship, name, [comment.post?.index]);
};
const ourMention = post?.contents?.some((e) => {
return e?.mention && e?.mention === window.ship;
});
if (!highlighted) {
if (ourMention) {
highlighted = true;
}
}
const commentIndexArray = (comment.post?.index || '/').split('/');
const commentIndex = commentIndexArray[commentIndexArray.length - 1];
@ -95,6 +106,7 @@ export function CommentItem(props: CommentItemProps): ReactElement {
date={post?.['time-sent']}
unread={props.unread}
group={group}
isRelativeTime
>
<Row px="2" gapX="2" height="18px">
<Action bg="white" onClick={doCopy}>{copyDisplay}</Action>
@ -106,7 +118,7 @@ export function CommentItem(props: CommentItemProps): ReactElement {
borderRadius="1"
p="1"
mb="1"
backgroundColor={props.highlighted ? 'washedBlue' : 'white'}
backgroundColor={highlighted ? 'washedBlue' : 'white'}
transcluded={0}
api={api}
post={post}

View File

@ -23,6 +23,7 @@ import RichText from './RichText';
import { ProfileStatus } from './ProfileStatus';
import useSettingsState from '~/logic/state/settings';
import {useOutsideClick} from '~/logic/lib/useOutsideClick';
import {useCopy} from '~/logic/lib/useCopy';
import {useContact} from '~/logic/state/contact';
import {useHistory} from 'react-router-dom';
import {Portal} from './Portal';
@ -59,6 +60,7 @@ const ProfileOverlay = (props: ProfileOverlayProps) => {
const hideAvatars = useSettingsState(state => state.calm.hideAvatars);
const hideNicknames = useSettingsState(state => state.calm.hideNicknames);
const isOwn = useMemo(() => window.ship === ship, [ship]);
const { copyDisplay, doCopy, didCopy } = useCopy(`~${ship}`);
const contact = useContact(`~${ship}`)
const color = `#${uxToHex(contact?.color ?? '0x0')}`;
@ -188,9 +190,18 @@ const ProfileOverlay = (props: ProfileOverlayProps) => {
overflow='hidden'
whiteSpace='pre'
marginBottom='0'
cursor='pointer'
display={didCopy ? 'none' : 'block'}
onClick={doCopy}
>
{showNickname ? contact?.nickname : cite(ship)}
</Text>
<Text
fontWeight='600'
marginBottom='0'
>
{copyDisplay}
</Text>
</Row>
{isOwn ? (
<ProfileStatus

View File

@ -48,12 +48,14 @@ class RemoteContent extends Component<RemoteContentProps, RemoteContentState> {
this.state = {
unfold: props.unfold || false,
embed: undefined,
noCors: false
noCors: false,
showArrow: false
};
this.unfoldEmbed = this.unfoldEmbed.bind(this);
this.loadOembed = this.loadOembed.bind(this);
this.wrapInLink = this.wrapInLink.bind(this);
this.onError = this.onError.bind(this);
this.toggleArrow = this.toggleArrow.bind(this);
}
save = () => {
@ -128,7 +130,7 @@ return;
});
}
wrapInLink(contents, textOnly = false, unfold = false, unfoldEmbed = null, embedContainer = null) {
wrapInLink(contents, textOnly = false, unfold = false, unfoldEmbed = null, embedContainer = null, flushPadding = false, noOp = false) {
const { style } = this.props;
return (
<Box borderRadius="1" backgroundColor="washedGray" maxWidth="min(100%, 20rem)">
@ -145,8 +147,8 @@ return;
)}
<BaseAnchor
display="flex"
p="2"
onClick={(e) => { e.stopPropagation(); }}
p={flushPadding ? 0 : 2}
onClick={(e) => { noOp ? e.preventDefault() : e.stopPropagation() }}
href={this.props.url}
whiteSpace="nowrap"
overflow="hidden"
@ -157,7 +159,8 @@ return;
style={{ color: 'inherit', textDecoration: 'none', ...style }}
target="_blank"
rel="noopener noreferrer"
>
cursor={noOp ? 'default' : 'pointer'}
>
{contents}
</BaseAnchor>
</Row>
@ -171,11 +174,16 @@ return;
this.setState({ noCors: true });
}
toggleArrow() {
this.setState({showArrow: !this.state.showArrow})
}
render() {
const {
remoteContentPolicy,
url,
text,
transcluded,
renderUrl = true,
imageProps = {},
audioProps = {},
@ -192,22 +200,60 @@ return;
const isVideo = VIDEO_REGEX.test(url);
const isOembed = hasProvider(url);
const isTranscluded = () => {
return transcluded;
}
if (isImage && remoteContentPolicy.imageShown) {
return this.wrapInLink(
<BaseImage
{...(noCors ? {} : { crossOrigin: "anonymous" })}
referrerPolicy="no-referrer"
flexShrink={0}
src={url}
style={style}
onLoad={onLoad}
onError={this.onError}
height="100%"
width="100%"
objectFit="contain"
{...imageProps}
{...props}
/>
<Box
position='relative'
onMouseEnter={this.toggleArrow}
onMouseLeave={this.toggleArrow}
>
<BaseAnchor
position='absolute'
top={2}
right={2}
display={this.state.showArrow ? 'block' : 'none'}
target='_blank'
rel='noopener noreferrer'
onClick={(e) => {
e.stopPropagation();
}}
href={url}
>
<Box
backgroundColor='white'
padding={2}
borderRadius='50%'
display='flex'
>
<Icon icon='ArrowNorthEast' />
</Box>
</BaseAnchor>
<BaseImage
{...(noCors ? {} : { crossOrigin: 'anonymous' })}
referrerPolicy='no-referrer'
flexShrink={0}
src={url}
style={style}
onLoad={onLoad}
onError={this.onError}
height='100%'
width='100%'
objectFit='contain'
borderRadius={2}
{...imageProps}
{...props}
/>
</Box>,
false,
false,
null,
null,
true,
isTranscluded()
);
} else if (isAudio && remoteContentPolicy.audioShown) {
return (
@ -271,7 +317,6 @@ return;
display={this.state.unfold ? 'block' : 'none'}
className='embed-container'
style={style}
flexShrink={0}
onLoad={this.onLoad}
{...oembedProps}
{...props}

View File

@ -134,7 +134,7 @@ const StatusBar = (props) => {
mr={2}
onClick={() => props.history.push('/~landscape/messages')}
>
<Icon icon='Users' />
<Icon icon='Messages' />
</StatusBarItem>
<Dropdown
dropWidth='250px'

View File

@ -12,6 +12,7 @@ export type TimestampProps = BoxProps & {
date?: boolean;
time?: boolean;
relative?: boolean;
height?: string;
};
const Timestamp = (props: TimestampProps): ReactElement | null => {

View File

@ -130,7 +130,7 @@ export class OmniboxResult extends Component {
<Icon
display='inline-block'
verticalAlign='middle'
icon='Users'
icon='Messages'
mr='2'
size='18px'
color={iconFill}

View File

@ -58,7 +58,11 @@ function GraphContentWideInner(
width="fit-content"
maxWidth="min(500px, 100%)"
>
<RemoteContent key={content.url} url={content.url} />
<RemoteContent
key={content.url}
url={content.url}
transcluded={transcluded}
/>
</Box>
);
case "mention":

View File

@ -46,6 +46,7 @@ export function PostHeader(props) {
isRelativeTime={true}
showTime={false}
time={true}
lineHeight='1'
/>
<Dropdown
dropWidth="200px"

View File

@ -65,3 +65,13 @@
*/
u3_noun
u3s_cue_atom(u3_atom a);
/* u3s_sift_ud_bytes: parse @ud.
*/
u3_weak
u3s_sift_ud_bytes(c3_w len_w, c3_y* byt_y);
/* u3s_sift_ud: parse @ud.
*/
u3_weak
u3s_sift_ud(u3_atom a);

View File

@ -5,49 +5,16 @@
#include <ctype.h>
/* functions
*/
u3_noun
_parse_ud(u3_noun txt) {
c3_c* c = u3a_string(txt);
static inline u3_noun
_parse_ud(u3_noun a)
{
u3_weak pro;
// First character must represent a digit
c3_c* cur = c;
if (cur[0] > '9' || cur[0] < '0') {
u3a_free(c);
return u3_none;
}
u3_atom total = cur[0] - '0';
cur++;
int since_last_period = 0;
while (cur[0] != 0) {
since_last_period++;
if (cur[0] == '.') {
since_last_period = 0;
cur++;
continue;
}
if (cur[0] > '9' || cur[0] < '0') {
u3a_free(c);
u3z(total);
return u3_none;
}
total = u3ka_mul(total, 10);
total = u3ka_add(total, cur[0] - '0');
cur++;
if (since_last_period > 3) {
u3a_free(c);
u3z(total);
return u3_none;
}
if ( u3_none == (pro = u3s_sift_ud(u3x_atom(a))) ) {
return u3_nul;
}
u3a_free(c);
return u3nc(0, total);
return u3nc(u3_nul, pro);
}
static

View File

@ -313,12 +313,17 @@ u3i_word(c3_w dat_w)
u3_atom
u3i_chub(c3_d dat_d)
{
c3_w dat_w[2] = {
dat_d & 0xffffffffULL,
dat_d >> 32
};
if ( c3y == u3a_is_cat(dat_d) ) {
return (u3_atom)dat_d;
}
else {
c3_w dat_w[2] = {
dat_d & 0xffffffffULL,
dat_d >> 32
};
return u3i_words(2, dat_w);
return u3i_words(2, dat_w);
}
}
/* u3i_bytes(): Copy [a] bytes from [b] to an LSB first atom.

View File

@ -98,28 +98,22 @@ _cm_punt(u3_noun tax)
}
#endif
static void _write(int fd, const void *buf, size_t count)
{
if (count != write(fd, buf, count)){
u3l_log("write failed\r\n");
c3_assert(0);
}
}
/* _cm_emergency(): write emergency text to stderr, never failing.
*/
static void
_cm_emergency(c3_c* cap_c, c3_l sig_l)
{
_write(2, "\r\n", 2);
_write(2, cap_c, strlen(cap_c));
c3_i ret_i;
ret_i = write(2, "\r\n", 2);
ret_i = write(2, cap_c, strlen(cap_c));
if ( sig_l ) {
_write(2, ": ", 2);
_write(2, &sig_l, 4);
ret_i = write(2, ": ", 2);
ret_i = write(2, &sig_l, 4);
}
_write(2, "\r\n", 2);
ret_i = write(2, "\r\n", 2);
}
static void _cm_overflow(void *arg1, void *arg2, void *arg3)
@ -127,7 +121,7 @@ static void _cm_overflow(void *arg1, void *arg2, void *arg3)
(void)(arg1);
(void)(arg2);
(void)(arg3);
siglongjmp(u3_Signal, c3__over);
u3m_signal(c3__over);
}
/* _cm_signal_handle(): handle a signal in general.
@ -139,7 +133,7 @@ _cm_signal_handle(c3_l sig_l)
sigsegv_leave_handler(_cm_overflow, NULL, NULL, NULL);
}
else {
siglongjmp(u3_Signal, sig_l);
u3m_signal(sig_l);
}
}
@ -661,7 +655,10 @@ u3m_dump(void)
c3_i
u3m_bail(u3_noun how)
{
if ( (c3__exit == how) && (u3R == &u3H->rod_u) ) {
if ( &(u3H->rod_u) == u3R ) {
// XX set exit code
//
fprintf(stderr, "home: bailing out\r\n");
abort();
}
@ -688,8 +685,9 @@ u3m_bail(u3_noun how)
//
switch ( how ) {
case c3__foul:
case c3__meme:
case c3__oops: {
// XX set exit code
//
fprintf(stderr, "bailing out\r\n");
abort();
}
@ -700,6 +698,9 @@ u3m_bail(u3_noun how)
// choice but to use the signal process; and we require the flat
// form of how.
//
// XX JB: these seem unrecoverable, at least wrt memory management,
// so they've been disabled above for now
//
c3_assert(_(u3a_is_cat(how)));
u3m_signal(how);
}

View File

@ -1205,16 +1205,11 @@ u3r_mp(mpz_t a_mp,
else {
u3a_atom* b_u = u3a_to_ptr(b);
c3_w len_w = b_u->len_w;
c3_d bit_d = (c3_d)len_w << 5;
// avoid reallocation on import, if possible
//
if ( (len_w >> 27) ) {
mpz_init(a_mp);
}
else {
mpz_init2(a_mp, len_w << 5);
}
mpz_init2(a_mp, (c3_w)c3_min(bit_d, UINT32_MAX));
mpz_import(a_mp, len_w, -1, sizeof(c3_w), 0, 0, b_u->buf_w);
}
}

View File

@ -856,3 +856,128 @@ u3s_cue_atom(u3_atom a)
return u3s_cue_bytes((c3_d)len_w, byt_y);
}
#define DIGIT(a) ( ((a) >= '0') && ((a) <= '9') )
#define BLOCK(a) ( ('.' == (a)[0]) \
&& DIGIT(a[1]) \
&& DIGIT(a[2]) \
&& DIGIT(a[3]) )
/* u3s_sift_ud_bytes: parse @ud
*/
u3_weak
u3s_sift_ud_bytes(c3_w len_w, c3_y* byt_y)
{
c3_y num_y = len_w % 4; // leading digits length
c3_s val_s = 0; // leading digits value
// +ape:ag: just 0
//
if ( !len_w ) return u3_none;
if ( '0' == *byt_y ) return ( 1 == len_w ) ? (u3_noun)0 : u3_none;
// +ted:ab: leading nonzero (checked above), plus up to 2 digits
//
#define NEXT() do { \
if ( !DIGIT(*byt_y) ) return u3_none; \
val_s *= 10; \
val_s += *byt_y++ - '0'; \
} while (0)
switch ( num_y ) {
case 3: NEXT();
case 2: NEXT();
case 1: NEXT(); break;
case 0: return u3_none;
}
#undef NEXT
len_w -= num_y;
// +tid:ab: dot-prefixed 3-digit blocks
//
// avoid gmp allocation if possible
// - 19 decimal digits fit in 64 bits
// - 18 digits is 24 bytes with separators
//
if ( ((1 == num_y) && (24 >= len_w))
|| (20 >= len_w) )
{
c3_d val_d = val_s;
while ( len_w ) {
if ( !BLOCK(byt_y) ) return u3_none;
byt_y++;
val_d *= 10;
val_d += *byt_y++ - '0';
val_d *= 10;
val_d += *byt_y++ - '0';
val_d *= 10;
val_d += *byt_y++ - '0';
len_w -= 4;
}
return u3i_chub(val_d);
}
{
// avoid gmp realloc if possible
//
mpz_t a_mp;
{
c3_d bit_d = (c3_d)(len_w / 4) * 10;
mpz_init2(a_mp, (c3_w)c3_min(bit_d, UINT32_MAX));
mpz_set_ui(a_mp, val_s);
}
while ( len_w ) {
if ( !BLOCK(byt_y) ) {
mpz_clear(a_mp);
return u3_none;
}
byt_y++;
val_s = *byt_y++ - '0';
val_s *= 10;
val_s += *byt_y++ - '0';
val_s *= 10;
val_s += *byt_y++ - '0';
mpz_mul_ui(a_mp, a_mp, 1000);
mpz_add_ui(a_mp, a_mp, val_s);
len_w -= 4;
}
return u3i_mp(a_mp);
}
}
#undef BLOCK
#undef DIGIT
/* u3s_sift_ud: parse @ud.
*/
u3_weak
u3s_sift_ud(u3_atom a)
{
c3_w len_w = u3r_met(3, a);
c3_y* byt_y;
// XX assumes little-endian
//
if ( c3y == u3a_is_cat(a) ) {
byt_y = (c3_y*)&a;
}
else {
u3a_atom* vat_u = u3a_to_ptr(a);
byt_y = (c3_y*)vat_u->buf_w;
}
return u3s_sift_ud_bytes(len_w, byt_y);
}

View File

@ -9,6 +9,108 @@ _setup(void)
u3m_pave(c3y);
}
static inline c3_i
_ud_good(c3_w num_w, const c3_c* num_c)
{
u3_weak out;
if ( num_w != (out = u3s_sift_ud_bytes(strlen(num_c), (c3_y*)num_c)) ) {
if ( u3_none == out ) {
fprintf(stderr, "sift_ud: %s fail; expected %u\r\n", num_c, num_w);
}
else {
fprintf(stderr, "sift_ud: %s wrong; expected %u: actual %u\r\n", num_c, num_w, out);
}
return 0;
}
return 1;
}
static inline c3_i
_ud_fail(const c3_c* num_c)
{
u3_weak out;
if ( u3_none != (out = u3s_sift_ud_bytes(strlen(num_c), (c3_y*)num_c)) ) {
u3m_p("out", out);
fprintf(stderr, "sift_ud: %s expected fail\r\n", num_c);
return 0;
}
return 1;
}
static c3_i
_test_sift_ud(void)
{
c3_i ret_i = 1;
ret_i &= _ud_good(0, "0");
ret_i &= _ud_good(1, "1");
ret_i &= _ud_good(12, "12");
ret_i &= _ud_good(123, "123");
ret_i &= _ud_good(1234, "1.234");
ret_i &= _ud_good(12345, "12.345");
ret_i &= _ud_good(123456, "123.456");
ret_i &= _ud_good(1234567, "1.234.567");
ret_i &= _ud_good(12345678, "12.345.678");
ret_i &= _ud_good(123456789, "123.456.789");
ret_i &= _ud_good(100000000, "100.000.000");
ret_i &= _ud_good(101101101, "101.101.101");
ret_i &= _ud_good(201201201, "201.201.201");
ret_i &= _ud_good(302201100, "302.201.100");
ret_i &= _ud_fail("01");
ret_i &= _ud_fail("02");
ret_i &= _ud_fail("003");
ret_i &= _ud_fail("1234");
ret_i &= _ud_fail("1234.5");
ret_i &= _ud_fail("1234.567.8");
ret_i &= _ud_fail("1234.56..78.");
ret_i &= _ud_fail("123.45a");
ret_i &= _ud_fail(".123.456");
{
c3_c* num_c = "4.294.967.296";
u3_weak out = u3s_sift_ud_bytes(strlen(num_c), (c3_y*)num_c);
u3_atom pro = u3qc_bex(32);
if ( u3_none == out ) {
fprintf(stderr, "sift_ud: (bex 32) fail\r\n");
ret_i = 0;
}
if ( c3n == u3r_sing(pro, out) ) {
u3m_p("out", out);
fprintf(stderr, "sift_ud: (bex 32) wrong\r\n");
ret_i = 0;
}
u3z(out); u3z(pro);
}
{
c3_c* num_c = "340.282.366.920.938.463.463.374.607.431.768.211.456";
u3_weak out = u3s_sift_ud_bytes(strlen(num_c), (c3_y*)num_c);
u3_atom pro = u3qc_bex(128);
if ( u3_none == out ) {
fprintf(stderr, "sift_ud: (bex 128) fail\r\n");
ret_i = 0;
}
if ( c3n == u3r_sing(pro, out) ) {
u3m_p("out", out);
fprintf(stderr, "sift_ud: (bex 128) wrong\r\n");
ret_i = 0;
}
u3z(out); u3z(pro);
}
return ret_i;
}
static c3_i
_test_en_base16(void)
{
@ -400,6 +502,11 @@ _test_jets(void)
{
c3_i ret_i = 1;
if ( !_test_sift_ud() ) {
fprintf(stderr, "test jets: sift_ud: failed\r\n");
ret_i = 0;
}
if ( !_test_base16() ) {
fprintf(stderr, "test jets: base16: failed\r\n");
ret_i = 0;

View File

@ -559,13 +559,18 @@ _cttp_creq_new(u3_cttp* ctp_u, c3_l num_l, u3_noun hes)
return 0;
}
// Parse the url out of the new style url passed to us.
// parse the url out of the new style url passed to us.
//
u3_noun unit_pul = u3do("de-purl:html", u3k(url));
if (c3n == u3r_du(unit_pul)) {
u3l_log("cttp: url parsing failed\n");
if ( c3n == u3r_du(unit_pul) ) {
c3_c* url_c = u3r_string(url);
u3l_log("cttp: unable to parse url:\n %s\n", url_c);
c3_free(url_c);
u3z(hes);
return 0;
}
u3_noun pul = u3t(unit_pul);
u3_noun hat = u3h(pul); // +hart
@ -817,7 +822,7 @@ _cttp_creq_on_head(h2o_http1client_t* cli_u, const c3_c* err_c, c3_i ver_i,
*/
static h2o_http1client_head_cb
_cttp_creq_on_connect(h2o_http1client_t* cli_u, const c3_c* err_c,
h2o_iovec_t** vec_p, size_t* vec_t, c3_i* hed_i)
h2o_iovec_t** vec_u, size_t* vec_i, c3_i* hed_i)
{
u3_creq* ceq_u = (u3_creq *)cli_u->data;
@ -826,11 +831,16 @@ _cttp_creq_on_connect(h2o_http1client_t* cli_u, const c3_c* err_c,
return 0;
}
// serialize request (populate rub_u)
//
_cttp_creq_fire(ceq_u);
{
c3_w len_w;
ceq_u->vec_u = _cttp_bods_to_vec(ceq_u->rub_u, &len_w);
*vec_t = len_w;
*vec_p = ceq_u->vec_u;
*vec_i = len_w;
*vec_u = ceq_u->vec_u;
*hed_i = (0 == strcmp(ceq_u->met_c, "HEAD"));
}
@ -842,24 +852,28 @@ _cttp_creq_on_connect(h2o_http1client_t* cli_u, const c3_c* err_c,
static void
_cttp_creq_connect(u3_creq* ceq_u)
{
c3_assert(u3_csat_ripe == ceq_u->sat_e);
c3_assert(ceq_u->ipf_c);
c3_assert( u3_csat_ripe == ceq_u->sat_e );
c3_assert( ceq_u->ipf_c );
h2o_iovec_t ipf_u = h2o_iovec_init(ceq_u->ipf_c, strlen(ceq_u->ipf_c));
c3_s por_s = ceq_u->por_s ? ceq_u->por_s :
( c3y == ceq_u->sec ) ? 443 : 80;
// connect by IP
h2o_http1client_connect(&ceq_u->cli_u, ceq_u, &ceq_u->ctp_u->ctx_u, ipf_u,
por_s, c3y == ceq_u->sec, _cttp_creq_on_connect);
// set hostname for TLS handshake
// set hostname for TLS handshake
//
if ( ceq_u->hot_c && c3y == ceq_u->sec ) {
c3_free(ceq_u->cli_u->ssl.server_name);
ceq_u->cli_u->ssl.server_name = strdup(ceq_u->hot_c);
}
_cttp_creq_fire(ceq_u);
// connect by IP
//
{
h2o_iovec_t ipf_u = h2o_iovec_init(ceq_u->ipf_c, strlen(ceq_u->ipf_c));
c3_t tls_t = ( c3y == ceq_u->sec );
c3_s por_s = ( ceq_u->por_s )
? ceq_u->por_s
: ( tls_t ) ? 443 : 80;
h2o_http1client_connect(&ceq_u->cli_u, ceq_u, &ceq_u->ctp_u->ctx_u,
ipf_u, por_s, tls_t, _cttp_creq_on_connect);
}
}
/* _cttp_creq_resolve_cb(): cb upon IP address resolution
@ -981,7 +995,6 @@ _cttp_ef_http_client(u3_cttp* ctp_u, u3_noun tag, u3_noun dat)
ret_o = c3y;
}
else {
u3l_log("cttp: strange request (unparsable url)\n");
ret_o = c3n;
}
}

View File

@ -1 +1 @@
1.3
1.4