mirror of
https://github.com/urbit/shrub.git
synced 2024-11-28 05:22:27 +03:00
links: added delete and copy actions
This commit is contained in:
parent
1bad160173
commit
92cc485db3
@ -221,6 +221,7 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
|
||||
scrollWindow={scrollWindow}
|
||||
history={history}
|
||||
api={api}
|
||||
bg="white"
|
||||
className="fl pr3 v-top pt1"
|
||||
/>
|
||||
<Box flexGrow={1} display='block' className="clamp-message">
|
||||
|
@ -1,14 +1,15 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Row, Col, Anchor, Box, Text, BaseImage, Icon } from '@tlon/indigo-react';
|
||||
import { Row, Col, Anchor, Box, Text, BaseImage, Icon, Action } from '@tlon/indigo-react';
|
||||
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import { cite } from '~/logic/lib/util';
|
||||
import { writeText } from '~/logic/lib/util';
|
||||
import Author from '~/views/components/Author';
|
||||
|
||||
import { roleForShip } from '~/logic/lib/group';
|
||||
import { Contacts, GraphNode, Group, LocalUpdateRemoteContentPolicy, Rolodex } from '~/types';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { Dropdown } from '~/views/components/Dropdown';
|
||||
|
||||
interface LinkItemProps {
|
||||
node: GraphNode;
|
||||
@ -39,7 +40,7 @@ export const LinkItem = (props: LinkItemProps) => {
|
||||
);
|
||||
|
||||
const author = node.post.author;
|
||||
const index = node.post.index.split('/').join('-');
|
||||
const index = node.post.index.split('/')[1];
|
||||
const size = node.children ? node.children.size : 0;
|
||||
const contents = node.post.contents;
|
||||
const hostname = URLparser.exec(contents[1].url) ? URLparser.exec(contents[1].url)[4] : null;
|
||||
@ -49,35 +50,44 @@ export const LinkItem = (props: LinkItemProps) => {
|
||||
const ourRole = group ? roleForShip(group, window.ship) : undefined;
|
||||
const [ship, name] = resource.split('/');
|
||||
|
||||
const [locationText, setLocationText] = useState('Copy Link Location');
|
||||
|
||||
const copyLocation = () => {
|
||||
setLocationText('Copied');
|
||||
writeText(contents[1].url);
|
||||
setTimeout(() => {
|
||||
setLocationText('Copy Link Location');
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
const deleteLink = () => {
|
||||
if (confirm('Are you sure you want to delete this link?')) {
|
||||
api.graph.removeNodes(`~${ship}`, name, [node.post.index]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box width="100%" {...rest}>
|
||||
|
||||
{/* <Box width="100%">
|
||||
<Link to={`${baseUrl}/${index}`}>
|
||||
<Text color="gray">{size} comment{size > 1 ? 's' : null}</Text>
|
||||
</Link>
|
||||
{(ourRole === 'admin' || node.post.author === window.ship)
|
||||
&& (<Text color='red' ml='2' cursor='pointer' onClick={() => api.graph.removeNodes(`~${ship}`, name, [node.post.index])}>Delete</Text>)}
|
||||
</Box> */}
|
||||
|
||||
<Anchor
|
||||
lineHeight="tall"
|
||||
display='flex'
|
||||
flexDirection='column'
|
||||
style={{ textDecoration: 'none' }}
|
||||
href={contents[1].url}
|
||||
width="100%"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
p={2}
|
||||
borderColor='black'
|
||||
border={1}
|
||||
borderRadius={2}
|
||||
>
|
||||
<Text overflow='hidden' style={{ textOverflow: 'ellipsis', whiteSpace: 'pre' }} mb={2}>{contents[0].text}</Text>
|
||||
<Text color="gray" flexShrink={0}><Box display='flex'><Icon icon='ArrowExternal' mr={1}/>{hostname}</Box></Text>
|
||||
</Anchor>
|
||||
<Row minWidth='0' flexShrink={0} width="100%" justifyContent="space-between" py={3} bg="white">
|
||||
<Anchor
|
||||
lineHeight="tall"
|
||||
display='flex'
|
||||
flexDirection='column'
|
||||
style={{ textDecoration: 'none' }}
|
||||
href={contents[1].url}
|
||||
width="100%"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
p={2}
|
||||
color='washedGray'
|
||||
border={1}
|
||||
borderRadius={2}
|
||||
>
|
||||
<Text overflow='hidden' color="black" style={{ textOverflow: 'ellipsis', whiteSpace: 'pre' }} mb={2}>{contents[0].text}</Text>
|
||||
<Text color="gray" flexShrink={0}><Box display='flex'><Icon icon='ArrowExternal' mr={1}/>{hostname}</Box></Text>
|
||||
</Anchor>
|
||||
|
||||
<Row minWidth='0' flexShrink={0} width="100%" justifyContent="space-between" py={3} bg="white">
|
||||
|
||||
<Author
|
||||
showImage
|
||||
@ -89,18 +99,38 @@ export const LinkItem = (props: LinkItemProps) => {
|
||||
remoteContentPolicy={remoteContentPolicy}
|
||||
group={group}
|
||||
api={api}
|
||||
>
|
||||
|
||||
</Author>
|
||||
<Link to={`${baseUrl}/${index}`}>
|
||||
></Author>
|
||||
|
||||
<Box ml="auto" mr={1}>
|
||||
<Link to={`${baseUrl}/${index}`}>
|
||||
<Box display='flex'>
|
||||
<Icon color='blue' icon='Chat' />
|
||||
<Text color='blue' ml={1}>{node.children.size}</Text>
|
||||
</Box>
|
||||
</Link>
|
||||
</Link>
|
||||
</Box>
|
||||
|
||||
<Dropdown
|
||||
width="200px"
|
||||
alignX="right"
|
||||
alignY="top"
|
||||
options={
|
||||
<Col backgroundColor="white" border={1} borderRadius={1} borderColor="lightGray">
|
||||
<Row alignItems="center" p={1}>
|
||||
<Action bg="white" m={1} color="black" onClick={copyLocation}>{locationText}</Action>
|
||||
</Row>
|
||||
{(ourRole === 'admin' || node.post.author === window.ship) &&
|
||||
<Row alignItems="center" p={1}>
|
||||
<Action bg="white" m={1} color="red" destructive onClick={deleteLink}>Delete Link</Action>
|
||||
</Row>
|
||||
}
|
||||
</Col>
|
||||
}
|
||||
>
|
||||
<Icon display="block" icon="Ellipsis" color="gray" />
|
||||
</Dropdown>
|
||||
|
||||
</Row>
|
||||
</Box>
|
||||
);
|
||||
</Box>);
|
||||
};
|
||||
|
||||
|
@ -1,69 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import { cite } from '~/logic/lib/util';
|
||||
import moment from 'moment';
|
||||
import { Box, Text, Row } from '@tlon/indigo-react';
|
||||
import RichText from '~/views/components/RichText';
|
||||
|
||||
import OverlaySigil from '~/views/components/OverlaySigil';
|
||||
|
||||
export const CommentItem = (props) => {
|
||||
const {
|
||||
contact,
|
||||
color,
|
||||
hideAvatars,
|
||||
hideNicknames,
|
||||
api,
|
||||
association,
|
||||
group
|
||||
} = props;
|
||||
const content = props.post.contents[0].text;
|
||||
const sigilClass = contact ? '' : 'mix-blend-diff';
|
||||
const timeSent =
|
||||
moment.unix(props.post['time-sent'] / 1000).format('hh:mm a');
|
||||
|
||||
const showAvatar = props.avatar && !props.hideAvatars;
|
||||
const showNickname = props.nickname && !props.hideNicknames;
|
||||
const img = showAvatar
|
||||
? <img src={props.avatar} height={36} width={36} className="dib" />
|
||||
: <Sigil
|
||||
ship={`~${props.post.author}`}
|
||||
size={36}
|
||||
color={`#${props.color}`}
|
||||
classes={(!!props.member ? 'mix-blend-diff' : '')}
|
||||
/>;
|
||||
|
||||
return (
|
||||
<Box width="100%" py={3} opacity={props.pending ? '0.6' : '1'}>
|
||||
<Row backgroundColor='white'>
|
||||
<OverlaySigil
|
||||
ship={`~${props.post.author}`}
|
||||
contact={contact}
|
||||
color={color}
|
||||
sigilClass={sigilClass}
|
||||
group={group}
|
||||
hideAvatars={hideAvatars}
|
||||
hideNicknames={hideNicknames}
|
||||
scrollWindow={document.createElement('div')}
|
||||
history={history}
|
||||
api={api}
|
||||
className="fl pr3 v-top bg-white bg-gray0-d pt1"
|
||||
/>
|
||||
<Row fontSize={0} alignItems="center" ml={2}>
|
||||
<Text mono={!props.hasNickname} title={props.post.author}>
|
||||
{showNickname ? props.nickname : cite(props.post.author)}
|
||||
</Text>
|
||||
<Text gray ml={2}>{timeSent}</Text>
|
||||
</Row>
|
||||
</Row>
|
||||
<Row>
|
||||
<Text display="block" py={3} fontSize={1}>
|
||||
<RichText remoteContentPolicy={props.remoteContentPolicy}>
|
||||
{content}
|
||||
</RichText>
|
||||
</Text>
|
||||
</Row>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import { RouteComponentProps } from "react-router-dom";
|
||||
import Note from "./Note";
|
||||
import { EditPost } from "./EditPost";
|
||||
|
||||
import { GraphNode, Graph, Contacts, LocalUpdateRemoteContentPolicy } from "~/types";
|
||||
import { GraphNode, Graph, Contacts, LocalUpdateRemoteContentPolicy, Group } from "~/types";
|
||||
|
||||
interface NoteRoutesProps {
|
||||
ship: string;
|
||||
@ -24,8 +24,6 @@ interface NoteRoutesProps {
|
||||
}
|
||||
|
||||
export function NoteRoutes(props: NoteRoutesProps & RouteComponentProps) {
|
||||
const { ship, book, noteId } = props;
|
||||
|
||||
const baseUrl = props.baseUrl || '/~404';
|
||||
const rootUrl = props.rootUrl || '/~404';
|
||||
|
||||
|
@ -83,7 +83,7 @@ export function NotebookRoutes(
|
||||
path={relativePath("/note/:noteId")}
|
||||
render={(routeProps) => {
|
||||
const { noteId } = routeProps.match.params;
|
||||
const noteIdNum = bigInt(noteId)
|
||||
const noteIdNum = bigInt(noteId);
|
||||
|
||||
if(!graph) {
|
||||
return <Center height="100%"><LoadingSpinner /></Center>;
|
||||
|
@ -7,6 +7,7 @@ import { uxToHex, cite } from "~/logic/lib/util";
|
||||
import { Contacts, Rolodex } from "~/types/contact-update";
|
||||
import OverlaySigil from "./OverlaySigil";
|
||||
import { Group, Association, LocalUpdateRemoteContentPolicy } from "~/types";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
|
||||
interface AuthorProps {
|
||||
contacts: Rolodex;
|
||||
@ -18,11 +19,12 @@ interface AuthorProps {
|
||||
children?: ReactNode;
|
||||
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
|
||||
group: Group;
|
||||
api: GlobalApi;
|
||||
}
|
||||
|
||||
export default function Author(props: AuthorProps) {
|
||||
const { contacts, ship = '', date, showImage, hideAvatars, hideNicknames, remoteContentPolicy, group, api } = props;
|
||||
let contact = null;
|
||||
let contact;
|
||||
if (contacts) {
|
||||
contact = ship in contacts ? contacts[ship] : null;
|
||||
}
|
||||
@ -43,9 +45,9 @@ export default function Author(props: AuthorProps) {
|
||||
group={group}
|
||||
hideAvatars={hideAvatars}
|
||||
hideNicknames={hideNicknames}
|
||||
scrollWindow={document.createElement('div')}
|
||||
history={history}
|
||||
api={api}
|
||||
bg="white"
|
||||
className="fl v-top pt1"
|
||||
/>
|
||||
</Box>
|
||||
|
@ -7,17 +7,18 @@ import {
|
||||
ProfileOverlay,
|
||||
OVERLAY_HEIGHT
|
||||
} from './ProfileOverlay';
|
||||
import { Box, BaseImage } from '@tlon/indigo-react';
|
||||
|
||||
interface OverlaySigilProps {
|
||||
import { Box, BaseImage, ColProps } from '@tlon/indigo-react';
|
||||
|
||||
type OverlaySigilProps = ColProps & {
|
||||
ship: string;
|
||||
contact?: Contact;
|
||||
color: string;
|
||||
sigilClass: string;
|
||||
group: Group;
|
||||
group?: Group;
|
||||
hideAvatars: boolean;
|
||||
hideNicknames: boolean;
|
||||
scrollWindow: HTMLElement;
|
||||
scrollWindow?: HTMLElement;
|
||||
history: any;
|
||||
api: any;
|
||||
className: string;
|
||||
@ -25,8 +26,8 @@ interface OverlaySigilProps {
|
||||
|
||||
interface OverlaySigilState {
|
||||
clicked: boolean;
|
||||
topSpace: number;
|
||||
bottomSpace: number;
|
||||
topSpace: number | 'auto';
|
||||
bottomSpace: number | 'auto';
|
||||
}
|
||||
|
||||
export default class OverlaySigil extends PureComponent<OverlaySigilProps, OverlaySigilState> {
|
||||
@ -50,12 +51,12 @@ export default class OverlaySigil extends PureComponent<OverlaySigilProps, Overl
|
||||
profileShow() {
|
||||
this.updateContainerOffset();
|
||||
this.setState({ clicked: true });
|
||||
this.props.scrollWindow.addEventListener('scroll', this.updateContainerOffset);
|
||||
this.props.scrollWindow?.addEventListener('scroll', this.updateContainerOffset);
|
||||
}
|
||||
|
||||
profileHide() {
|
||||
this.setState({ clicked: false });
|
||||
this.props.scrollWindow.removeEventListener('scroll', this.updateContainerOffset, true);
|
||||
this.props.scrollWindow?.removeEventListener('scroll', this.updateContainerOffset, true);
|
||||
}
|
||||
|
||||
updateContainerOffset() {
|
||||
@ -63,8 +64,12 @@ export default class OverlaySigil extends PureComponent<OverlaySigilProps, Overl
|
||||
const container = this.containerRef.current;
|
||||
const scrollWindow = this.props.scrollWindow;
|
||||
|
||||
const bottomSpace = scrollWindow.scrollHeight - container.offsetTop - scrollWindow.scrollTop;
|
||||
const topSpace = scrollWindow.offsetHeight - bottomSpace - OVERLAY_HEIGHT;
|
||||
const bottomSpace = scrollWindow
|
||||
? scrollWindow.scrollHeight - container.offsetTop - scrollWindow.scrollTop
|
||||
: 'auto';
|
||||
const topSpace = scrollWindow
|
||||
? scrollWindow.offsetHeight - bottomSpace - OVERLAY_HEIGHT
|
||||
: 0;
|
||||
|
||||
this.setState({
|
||||
topSpace,
|
||||
@ -78,16 +83,29 @@ export default class OverlaySigil extends PureComponent<OverlaySigilProps, Overl
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
const { hideAvatars } = props;
|
||||
const {
|
||||
className,
|
||||
ship,
|
||||
contact,
|
||||
color,
|
||||
group,
|
||||
hideAvatars,
|
||||
hideNicknames,
|
||||
history,
|
||||
api,
|
||||
sigilClass,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
const img = (props.contact && (props.contact.avatar !== null) && !hideAvatars)
|
||||
? <BaseImage display='inline-block' src={props.contact.avatar} height={16} width={16} />
|
||||
const { state } = this;
|
||||
|
||||
const img = (contact && (contact.avatar !== null) && !hideAvatars)
|
||||
? <BaseImage display='inline-block' src={contact.avatar} height={16} width={16} />
|
||||
: <Sigil
|
||||
ship={props.ship}
|
||||
ship={ship}
|
||||
size={16}
|
||||
color={props.color}
|
||||
classes={props.sigilClass}
|
||||
color={color}
|
||||
classes={sigilClass}
|
||||
icon
|
||||
padded
|
||||
/>;
|
||||
@ -97,22 +115,23 @@ export default class OverlaySigil extends PureComponent<OverlaySigilProps, Overl
|
||||
cursor='pointer'
|
||||
position='relative'
|
||||
onClick={this.profileShow}
|
||||
className={props.className}
|
||||
ref={this.containerRef}
|
||||
ref={this.containerRef}
|
||||
className={className}
|
||||
>
|
||||
{state.clicked && (
|
||||
<ProfileOverlay
|
||||
ship={props.ship}
|
||||
contact={props.contact}
|
||||
color={props.color}
|
||||
ship={ship}
|
||||
contact={contact}
|
||||
color={color}
|
||||
topSpace={state.topSpace}
|
||||
bottomSpace={state.bottomSpace}
|
||||
group={props.group}
|
||||
group={group}
|
||||
onDismiss={this.profileHide}
|
||||
hideAvatars={hideAvatars}
|
||||
hideNicknames={props.hideNicknames}
|
||||
history={props.history}
|
||||
api={props.api}
|
||||
hideNicknames={hideNicknames}
|
||||
history={history}
|
||||
api={api}
|
||||
{...rest}
|
||||
/>
|
||||
)}
|
||||
{img}
|
||||
|
@ -4,17 +4,17 @@ import { Contact, Group } from '~/types';
|
||||
import { cite } from '~/logic/lib/util';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
|
||||
import { Box, Col, Button, Text, BaseImage } from '@tlon/indigo-react';
|
||||
import { Box, Col, Button, Text, BaseImage, ColProps } from '@tlon/indigo-react';
|
||||
|
||||
export const OVERLAY_HEIGHT = 250;
|
||||
|
||||
interface ProfileOverlayProps {
|
||||
type ProfileOverlayProps = ColProps & {
|
||||
ship: string;
|
||||
contact?: Contact;
|
||||
color: string;
|
||||
topSpace: number;
|
||||
bottomSpace: number;
|
||||
group: Group;
|
||||
topSpace: number | 'auto';
|
||||
bottomSpace: number | 'auto';
|
||||
group?: Group;
|
||||
onDismiss(): void;
|
||||
hideAvatars: boolean;
|
||||
hideNicknames: boolean;
|
||||
@ -53,7 +53,19 @@ export class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { contact, ship, color, topSpace, bottomSpace, group, hideNicknames, hideAvatars, history } = this.props;
|
||||
const {
|
||||
contact,
|
||||
ship,
|
||||
color,
|
||||
topSpace,
|
||||
bottomSpace,
|
||||
group = false,
|
||||
hideNicknames,
|
||||
hideAvatars,
|
||||
history,
|
||||
onDismiss,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
let top, bottom;
|
||||
if (topSpace < OVERLAY_HEIGHT / 2) {
|
||||
@ -84,7 +96,7 @@ export class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
|
||||
/* if (!group.hidden) {
|
||||
}*/
|
||||
|
||||
const isHidden = group.hidden;
|
||||
const isHidden = group ? group.hidden : false;
|
||||
|
||||
return (
|
||||
<Col
|
||||
@ -95,6 +107,7 @@ export class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
|
||||
zIndex='3'
|
||||
fontSize='0'
|
||||
style={containerStyle}
|
||||
{...rest}
|
||||
>
|
||||
<Box height='160px' width='160px'>
|
||||
{img}
|
||||
|
Loading…
Reference in New Issue
Block a user