Remove usage of tiley (#4406)
* First pass * Mooarrr * lint * snapshots
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
import { PlusIcon } from "outline-icons";
|
import { PlusIcon } from "outline-icons";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
import { stringToColor } from "@shared/utils/color";
|
||||||
import TeamNew from "~/scenes/TeamNew";
|
import TeamNew from "~/scenes/TeamNew";
|
||||||
|
import TeamLogo from "~/components/TeamLogo";
|
||||||
import { createAction } from "~/actions";
|
import { createAction } from "~/actions";
|
||||||
import { loadSessionsFromCookie } from "~/hooks/useSessions";
|
import { loadSessionsFromCookie } from "~/hooks/useSessions";
|
||||||
import { TeamSection } from "../sections";
|
import { TeamSection } from "../sections";
|
||||||
@@ -11,7 +13,18 @@ export const switchTeamList = getSessions().map((session) => {
|
|||||||
name: session.name,
|
name: session.name,
|
||||||
section: TeamSection,
|
section: TeamSection,
|
||||||
keywords: "change switch workspace organization team",
|
keywords: "change switch workspace organization team",
|
||||||
icon: () => <Logo alt={session.name} src={session.logoUrl} />,
|
icon: () => (
|
||||||
|
<StyledTeamLogo
|
||||||
|
alt={session.name}
|
||||||
|
model={{
|
||||||
|
initial: session.name[0],
|
||||||
|
avatarUrl: session.logoUrl,
|
||||||
|
id: session.teamId,
|
||||||
|
color: stringToColor(session.teamId),
|
||||||
|
}}
|
||||||
|
size={24}
|
||||||
|
/>
|
||||||
|
),
|
||||||
visible: ({ currentTeamId }) => currentTeamId !== session.teamId,
|
visible: ({ currentTeamId }) => currentTeamId !== session.teamId,
|
||||||
perform: () => (window.location.href = session.url),
|
perform: () => (window.location.href = session.url),
|
||||||
});
|
});
|
||||||
@@ -55,10 +68,9 @@ function getSessions(params?: { exclude?: string }) {
|
|||||||
return otherSessions;
|
return otherSessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Logo = styled("img")`
|
const StyledTeamLogo = styled(TeamLogo)`
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
width: 24px;
|
border: 0;
|
||||||
height: 24px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const rootTeamActions = [switchTeam, createTeam];
|
export const rootTeamActions = [switchTeam, createTeam];
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import User from "~/models/User";
|
|
||||||
import useBoolean from "~/hooks/useBoolean";
|
import useBoolean from "~/hooks/useBoolean";
|
||||||
|
import Initials from "./Initials";
|
||||||
import placeholder from "./placeholder.png";
|
import placeholder from "./placeholder.png";
|
||||||
|
|
||||||
|
export interface IAvatar {
|
||||||
|
avatarUrl: string | null;
|
||||||
|
color: string;
|
||||||
|
initial: string;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
src: string;
|
|
||||||
size: number;
|
size: number;
|
||||||
|
src?: string;
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
user?: User;
|
model?: IAvatar;
|
||||||
alt?: string;
|
alt?: string;
|
||||||
showBorder?: boolean;
|
showBorder?: boolean;
|
||||||
onClick?: React.MouseEventHandler<HTMLImageElement>;
|
onClick?: React.MouseEventHandler<HTMLImageElement>;
|
||||||
@@ -16,20 +23,28 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function Avatar(props: Props) {
|
function Avatar(props: Props) {
|
||||||
const { src, icon, showBorder, ...rest } = props;
|
const { icon, showBorder, model, ...rest } = props;
|
||||||
|
const src = props.src || model?.avatarUrl;
|
||||||
const [error, handleError] = useBoolean(false);
|
const [error, handleError] = useBoolean(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AvatarWrapper>
|
<Relative>
|
||||||
<CircleImg
|
{src ? (
|
||||||
onError={handleError}
|
<CircleImg
|
||||||
src={error ? placeholder : src}
|
onError={handleError}
|
||||||
$showBorder={showBorder}
|
src={error ? placeholder : src}
|
||||||
{...rest}
|
$showBorder={showBorder}
|
||||||
/>
|
{...rest}
|
||||||
|
/>
|
||||||
|
) : model ? (
|
||||||
|
<Initials color={model.color} $showBorder={showBorder} {...rest}>
|
||||||
|
{model.initial}
|
||||||
|
</Initials>
|
||||||
|
) : (
|
||||||
|
<Initials $showBorder={showBorder} {...rest} />
|
||||||
|
)}
|
||||||
{icon && <IconWrapper>{icon}</IconWrapper>}
|
{icon && <IconWrapper>{icon}</IconWrapper>}
|
||||||
</AvatarWrapper>
|
</Relative>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +52,7 @@ Avatar.defaultProps = {
|
|||||||
size: 24,
|
size: 24,
|
||||||
};
|
};
|
||||||
|
|
||||||
const AvatarWrapper = styled.div`
|
const Relative = styled.div`
|
||||||
position: relative;
|
position: relative;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ function AvatarWithPresence({
|
|||||||
$isObserving={isObserving}
|
$isObserving={isObserving}
|
||||||
$color={user.color}
|
$color={user.color}
|
||||||
>
|
>
|
||||||
<Avatar src={user.avatarUrl} onClick={onClick} size={32} />
|
<Avatar model={user} onClick={onClick} size={32} />
|
||||||
</AvatarWrapper>
|
</AvatarWrapper>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</>
|
</>
|
||||||
|
|||||||
27
app/components/Avatar/Initials.tsx
Normal file
27
app/components/Avatar/Initials.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import styled from "styled-components";
|
||||||
|
import Flex from "~/components/Flex";
|
||||||
|
|
||||||
|
const Initials = styled(Flex)<{
|
||||||
|
color?: string;
|
||||||
|
size: number;
|
||||||
|
$showBorder?: boolean;
|
||||||
|
}>`
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
color: #fff;
|
||||||
|
background-color: ${(props) => props.color};
|
||||||
|
width: ${(props) => props.size}px;
|
||||||
|
height: ${(props) => props.size}px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid
|
||||||
|
${(props) =>
|
||||||
|
props.$showBorder === false ? "transparent" : props.theme.background};
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: ${(props) => props.size / 2}px;
|
||||||
|
font-weight: 500;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Initials;
|
||||||
@@ -43,10 +43,10 @@ function DocumentViews({ document, isOpen }: Props) {
|
|||||||
<PaginatedList
|
<PaginatedList
|
||||||
aria-label={t("Viewers")}
|
aria-label={t("Viewers")}
|
||||||
items={users}
|
items={users}
|
||||||
renderItem={(item: User) => {
|
renderItem={(model: User) => {
|
||||||
const view = documentViews.find((v) => v.user.id === item.id);
|
const view = documentViews.find((v) => v.user.id === model.id);
|
||||||
const isPresent = presentIds.includes(item.id);
|
const isPresent = presentIds.includes(model.id);
|
||||||
const isEditing = editingIds.includes(item.id);
|
const isEditing = editingIds.includes(model.id);
|
||||||
const subtitle = isPresent
|
const subtitle = isPresent
|
||||||
? isEditing
|
? isEditing
|
||||||
? t("Currently editing")
|
? t("Currently editing")
|
||||||
@@ -58,10 +58,10 @@ function DocumentViews({ document, isOpen }: Props) {
|
|||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<ListItem
|
<ListItem
|
||||||
key={item.id}
|
key={model.id}
|
||||||
title={item.name}
|
title={model.name}
|
||||||
subtitle={subtitle}
|
subtitle={subtitle}
|
||||||
image={<Avatar key={item.id} src={item.avatarUrl} size={32} />}
|
image={<Avatar key={model.id} model={model} size={32} />}
|
||||||
border={false}
|
border={false}
|
||||||
small
|
small
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ const EventListItem = ({ event, latest, document, ...rest }: Props) => {
|
|||||||
onClick={handleTimeClick}
|
onClick={handleTimeClick}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
image={<Avatar src={event.actor?.avatarUrl} size={32} />}
|
image={<Avatar model={event.actor} size={32} />}
|
||||||
subtitle={
|
subtitle={
|
||||||
<Subtitle>
|
<Subtitle>
|
||||||
{icon}
|
{icon}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ function Facepile({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function DefaultAvatar(user: User) {
|
function DefaultAvatar(user: User) {
|
||||||
return <Avatar user={user} src={user.avatarUrl} size={32} />;
|
return <Avatar model={user} size={32} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AvatarWrapper = styled.div`
|
const AvatarWrapper = styled.div`
|
||||||
|
|||||||
@@ -63,14 +63,7 @@ function AppSidebar() {
|
|||||||
<SidebarButton
|
<SidebarButton
|
||||||
{...props}
|
{...props}
|
||||||
title={team.name}
|
title={team.name}
|
||||||
image={
|
image={<TeamLogo model={team} size={32} alt={t("Logo")} />}
|
||||||
<StyledTeamLogo
|
|
||||||
src={team.avatarUrl}
|
|
||||||
width={32}
|
|
||||||
height={32}
|
|
||||||
alt={t("Logo")}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
showDisclosure
|
showDisclosure
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -139,11 +132,6 @@ function AppSidebar() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledTeamLogo = styled(TeamLogo)`
|
|
||||||
margin-right: 4px;
|
|
||||||
background: white;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Drafts = styled(Text)`
|
const Drafts = styled(Text)`
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(
|
|||||||
image={
|
image={
|
||||||
<StyledAvatar
|
<StyledAvatar
|
||||||
alt={user.name}
|
alt={user.name}
|
||||||
src={user.avatarUrl}
|
model={user}
|
||||||
size={24}
|
size={24}
|
||||||
showBorder={false}
|
showBorder={false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
import Avatar from "./Avatar";
|
||||||
|
|
||||||
const TeamLogo = styled.img<{ width?: number; height?: number; size?: string }>`
|
const TeamLogo = styled(Avatar)`
|
||||||
width: ${(props) =>
|
|
||||||
props.width ? `${props.width}px` : props.size || "auto"};
|
|
||||||
height: ${(props) =>
|
|
||||||
props.height ? `${props.height}px` : props.size || "38px"};
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid ${(props) => props.theme.divider};
|
border: 1px solid ${(props) => props.theme.divider};
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { computed, observable } from "mobx";
|
import { computed, observable } from "mobx";
|
||||||
import { TeamPreference, TeamPreferences } from "@shared/types";
|
import { TeamPreference, TeamPreferences } from "@shared/types";
|
||||||
|
import { stringToColor } from "@shared/utils/color";
|
||||||
import BaseModel from "./BaseModel";
|
import BaseModel from "./BaseModel";
|
||||||
import Field from "./decorators/Field";
|
import Field from "./decorators/Field";
|
||||||
|
|
||||||
@@ -69,6 +70,16 @@ class Team extends BaseModel {
|
|||||||
return "SSO";
|
return "SSO";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
|
get color(): string {
|
||||||
|
return stringToColor(this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
|
get initial(): string {
|
||||||
|
return this.name ? this.name[0] : "?";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether this team is using a separate editing mode behind an "Edit"
|
* Returns whether this team is using a separate editing mode behind an "Edit"
|
||||||
* button rather than seamless always-editing.
|
* button rather than seamless always-editing.
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ class User extends ParanoidModel {
|
|||||||
|
|
||||||
isSuspended: boolean;
|
isSuspended: boolean;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
get initial(): string {
|
||||||
|
return this.name ? this.name[0] : "?";
|
||||||
|
}
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
get isInvited(): boolean {
|
get isInvited(): boolean {
|
||||||
return !this.lastActiveAt;
|
return !this.lastActiveAt;
|
||||||
|
|||||||
@@ -104,18 +104,16 @@ const MembershipPreview = ({ collection, limit = 8 }: Props) => {
|
|||||||
users={sortBy(collectionUsers, "lastActiveAt")}
|
users={sortBy(collectionUsers, "lastActiveAt")}
|
||||||
overflow={overflow}
|
overflow={overflow}
|
||||||
limit={limit}
|
limit={limit}
|
||||||
renderAvatar={(user) => (
|
renderAvatar={(user) => <StyledAvatar model={user} size={32} />}
|
||||||
<StyledAvatar user={user} src={user.avatarUrl} size={32} />
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
</Fade>
|
</Fade>
|
||||||
</NudeButton>
|
</NudeButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledAvatar = styled(Avatar)<{ user: User }>`
|
const StyledAvatar = styled(Avatar)<{ model: User }>`
|
||||||
transition: opacity 250ms ease-in-out;
|
transition: opacity 250ms ease-in-out;
|
||||||
opacity: ${(props) => (props.user.isRecentlyActive ? 1 : 0.5)};
|
opacity: ${(props) => (props.model.isRecentlyActive ? 1 : 0.5)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default observer(MembershipPreview);
|
export default observer(MembershipPreview);
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const MemberListItem = ({
|
|||||||
{user.isAdmin && <Badge primary={user.isAdmin}>{t("Admin")}</Badge>}
|
{user.isAdmin && <Badge primary={user.isAdmin}>{t("Admin")}</Badge>}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
image={<Avatar src={user.avatarUrl} size={32} />}
|
image={<Avatar model={user} size={32} />}
|
||||||
actions={
|
actions={
|
||||||
<Flex align="center" gap={8}>
|
<Flex align="center" gap={8}>
|
||||||
{onUpdate && (
|
{onUpdate && (
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const UserListItem = ({ user, onAdd, canEdit }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<ListItem
|
<ListItem
|
||||||
title={user.name}
|
title={user.name}
|
||||||
image={<Avatar src={user.avatarUrl} size={32} />}
|
image={<Avatar model={user} size={32} />}
|
||||||
subtitle={
|
subtitle={
|
||||||
<>
|
<>
|
||||||
{user.lastActiveAt ? (
|
{user.lastActiveAt ? (
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const GroupMemberListItem = ({ user, onRemove, onAdd }: Props) => {
|
|||||||
{user.isAdmin && <Badge primary={user.isAdmin}>{t("Admin")}</Badge>}
|
{user.isAdmin && <Badge primary={user.isAdmin}>{t("Admin")}</Badge>}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
image={<Avatar src={user.avatarUrl} size={32} />}
|
image={<Avatar model={user} size={32} />}
|
||||||
actions={
|
actions={
|
||||||
<Flex align="center">
|
<Flex align="center">
|
||||||
{onRemove && <GroupMemberMenu onRemove={onRemove} />}
|
{onRemove && <GroupMemberMenu onRemove={onRemove} />}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const UserListItem = ({ user, onAdd, canEdit }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<ListItem
|
<ListItem
|
||||||
title={user.name}
|
title={user.name}
|
||||||
image={<Avatar src={user.avatarUrl} size={32} />}
|
image={<Avatar model={user} size={32} />}
|
||||||
subtitle={
|
subtitle={
|
||||||
<>
|
<>
|
||||||
{user.lastActiveAt ? (
|
{user.lastActiveAt ? (
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ function Login({ children }: Props) {
|
|||||||
/>
|
/>
|
||||||
<Logo>
|
<Logo>
|
||||||
{config.logo ? (
|
{config.logo ? (
|
||||||
<TeamLogo width={48} height={48} src={config.logo} />
|
<TeamLogo size={48} src={config.logo} />
|
||||||
) : (
|
) : (
|
||||||
<OutlineLogo size={42} fill="currentColor" />
|
<OutlineLogo size={42} fill="currentColor" />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ function Details() {
|
|||||||
const form = useRef<HTMLFormElement>(null);
|
const form = useRef<HTMLFormElement>(null);
|
||||||
const [name, setName] = useState(team.name);
|
const [name, setName] = useState(team.name);
|
||||||
const [subdomain, setSubdomain] = useState(team.subdomain);
|
const [subdomain, setSubdomain] = useState(team.subdomain);
|
||||||
const [avatarUrl, setAvatarUrl] = useState<string>(team.avatarUrl);
|
|
||||||
const [defaultCollectionId, setDefaultCollectionId] = useState<string | null>(
|
const [defaultCollectionId, setDefaultCollectionId] = useState<string | null>(
|
||||||
team.defaultCollectionId
|
team.defaultCollectionId
|
||||||
);
|
);
|
||||||
@@ -40,7 +39,6 @@ function Details() {
|
|||||||
try {
|
try {
|
||||||
await auth.updateTeam({
|
await auth.updateTeam({
|
||||||
name,
|
name,
|
||||||
avatarUrl,
|
|
||||||
subdomain,
|
subdomain,
|
||||||
defaultCollectionId,
|
defaultCollectionId,
|
||||||
});
|
});
|
||||||
@@ -53,7 +51,7 @@ function Details() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[auth, name, avatarUrl, subdomain, defaultCollectionId, showToast, t]
|
[auth, name, subdomain, defaultCollectionId, showToast, t]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleNameChange = React.useCallback(
|
const handleNameChange = React.useCallback(
|
||||||
@@ -71,7 +69,6 @@ function Details() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleAvatarUpload = async (avatarUrl: string) => {
|
const handleAvatarUpload = async (avatarUrl: string) => {
|
||||||
setAvatarUrl(avatarUrl);
|
|
||||||
await auth.updateTeam({
|
await auth.updateTeam({
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
});
|
});
|
||||||
@@ -115,7 +112,7 @@ function Details() {
|
|||||||
<ImageInput
|
<ImageInput
|
||||||
onSuccess={handleAvatarUpload}
|
onSuccess={handleAvatarUpload}
|
||||||
onError={handleAvatarError}
|
onError={handleAvatarError}
|
||||||
src={avatarUrl}
|
model={team}
|
||||||
borderRadius={0}
|
borderRadius={0}
|
||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ const Profile = () => {
|
|||||||
const user = useCurrentUser();
|
const user = useCurrentUser();
|
||||||
const form = React.useRef<HTMLFormElement>(null);
|
const form = React.useRef<HTMLFormElement>(null);
|
||||||
const [name, setName] = React.useState<string>(user.name || "");
|
const [name, setName] = React.useState<string>(user.name || "");
|
||||||
const [avatarUrl, setAvatarUrl] = React.useState<string>(user.avatarUrl);
|
|
||||||
const { showToast } = useToasts();
|
const { showToast } = useToasts();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -28,7 +27,6 @@ const Profile = () => {
|
|||||||
try {
|
try {
|
||||||
await auth.updateUser({
|
await auth.updateUser({
|
||||||
name,
|
name,
|
||||||
avatarUrl,
|
|
||||||
});
|
});
|
||||||
showToast(t("Profile saved"), {
|
showToast(t("Profile saved"), {
|
||||||
type: "success",
|
type: "success",
|
||||||
@@ -45,7 +43,6 @@ const Profile = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAvatarUpload = async (avatarUrl: string) => {
|
const handleAvatarUpload = async (avatarUrl: string) => {
|
||||||
setAvatarUrl(avatarUrl);
|
|
||||||
await auth.updateUser({
|
await auth.updateUser({
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
});
|
});
|
||||||
@@ -79,7 +76,7 @@ const Profile = () => {
|
|||||||
<ImageInput
|
<ImageInput
|
||||||
onSuccess={handleAvatarUpload}
|
onSuccess={handleAvatarUpload}
|
||||||
onError={handleAvatarError}
|
onError={handleAvatarError}
|
||||||
src={avatarUrl}
|
model={user}
|
||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingRow
|
<SettingRow
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
import Avatar, { IAvatar } from "~/components/Avatar/Avatar";
|
||||||
import Flex from "~/components/Flex";
|
import Flex from "~/components/Flex";
|
||||||
import ImageUpload, { Props as ImageUploadProps } from "./ImageUpload";
|
import ImageUpload, { Props as ImageUploadProps } from "./ImageUpload";
|
||||||
|
|
||||||
type Props = ImageUploadProps & {
|
type Props = ImageUploadProps & {
|
||||||
src?: string;
|
model: IAvatar;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ImageInput({ src, ...rest }: Props) {
|
export default function ImageInput({ model, ...rest }: Props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ImageBox>
|
<ImageBox>
|
||||||
<ImageUpload {...rest}>
|
<ImageUpload {...rest}>
|
||||||
<Avatar src={src} />
|
<StyledAvatar model={model} size={64} />
|
||||||
<Flex auto align="center" justify="center">
|
<Flex auto align="center" justify="center" className="upload">
|
||||||
{t("Upload")}
|
{t("Upload")}
|
||||||
</Flex>
|
</Flex>
|
||||||
</ImageUpload>
|
</ImageUpload>
|
||||||
@@ -28,8 +29,8 @@ const avatarStyles = `
|
|||||||
height: 64px;
|
height: 64px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Avatar = styled.img`
|
const StyledAvatar = styled(Avatar)`
|
||||||
${avatarStyles};
|
border-radius: 8px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ImageBox = styled(Flex)`
|
const ImageBox = styled(Flex)`
|
||||||
@@ -41,7 +42,7 @@ const ImageBox = styled(Flex)`
|
|||||||
background: ${(props) => props.theme.background};
|
background: ${(props) => props.theme.background};
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
div div {
|
.upload {
|
||||||
${avatarStyles};
|
${avatarStyles};
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -53,7 +54,7 @@ const ImageBox = styled(Flex)`
|
|||||||
transition: all 250ms;
|
transition: all 250ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover div {
|
&:hover .upload {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
background: rgba(0, 0, 0, 0.75);
|
background: rgba(0, 0, 0, 0.75);
|
||||||
color: ${(props) => props.theme.white};
|
color: ${(props) => props.theme.white};
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ function PeopleTable({ canManage, ...rest }: Props) {
|
|||||||
Cell: observer(
|
Cell: observer(
|
||||||
({ value, row }: { value: string; row: { original: User } }) => (
|
({ value, row }: { value: string; row: { original: User } }) => (
|
||||||
<Flex align="center" gap={8}>
|
<Flex align="center" gap={8}>
|
||||||
<Avatar src={row.original.avatarUrl} size={32} /> {value}{" "}
|
<Avatar model={row.original} size={32} /> {value}{" "}
|
||||||
{currentUser.id === row.original.id && `(${t("You")})`}
|
{currentUser.id === row.original.id && `(${t("You")})`}
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ function SharesTable({ canManage, ...rest }: Props) {
|
|||||||
<Flex align="center" gap={4}>
|
<Flex align="center" gap={4}>
|
||||||
{row.original.createdBy && (
|
{row.original.createdBy && (
|
||||||
<Avatar
|
<Avatar
|
||||||
src={row.original.createdBy.avatarUrl}
|
model={row.original.createdBy}
|
||||||
alt={row.original.createdBy.name}
|
alt={row.original.createdBy.name}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const UserListItem = ({ user, showMenu }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<ListItem
|
<ListItem
|
||||||
title={<Title>{user.name}</Title>}
|
title={<Title>{user.name}</Title>}
|
||||||
image={<Avatar src={user.avatarUrl} size={32} />}
|
image={<Avatar model={user} size={32} />}
|
||||||
subtitle={
|
subtitle={
|
||||||
<>
|
<>
|
||||||
{user.email ? `${user.email} · ` : undefined}
|
{user.email ? `${user.email} · ` : undefined}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ function UserProfile(props: Props) {
|
|||||||
<Modal
|
<Modal
|
||||||
title={
|
title={
|
||||||
<Flex align="center">
|
<Flex align="center">
|
||||||
<Avatar src={user.avatarUrl} size={38} alt={t("Profile picture")} />
|
<Avatar model={user} size={38} alt={t("Profile picture")} />
|
||||||
<span> {user.name}</span>
|
<span> {user.name}</span>
|
||||||
</Flex>
|
</Flex>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,14 +226,17 @@ export default class AuthStore {
|
|||||||
preferences?: UserPreferences;
|
preferences?: UserPreferences;
|
||||||
}) => {
|
}) => {
|
||||||
this.isSaving = true;
|
this.isSaving = true;
|
||||||
|
const previousData = this.user?.toAPI();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
this.user?.updateFromJson(params);
|
||||||
const res = await client.post(`/users.update`, params);
|
const res = await client.post(`/users.update`, params);
|
||||||
invariant(res?.data, "User response not available");
|
invariant(res?.data, "User response not available");
|
||||||
runInAction("AuthStore#updateUser", () => {
|
this.user?.updateFromJson(res.data);
|
||||||
this.addPolicies(res.policies);
|
this.addPolicies(res.policies);
|
||||||
this.user = new User(res.data, this);
|
} catch (err) {
|
||||||
});
|
this.user?.updateFromJson(previousData);
|
||||||
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
this.isSaving = false;
|
this.isSaving = false;
|
||||||
}
|
}
|
||||||
@@ -251,14 +254,17 @@ export default class AuthStore {
|
|||||||
preferences?: TeamPreferences;
|
preferences?: TeamPreferences;
|
||||||
}) => {
|
}) => {
|
||||||
this.isSaving = true;
|
this.isSaving = true;
|
||||||
|
const previousData = this.team?.toAPI();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
this.team?.updateFromJson(params);
|
||||||
const res = await client.post(`/team.update`, params);
|
const res = await client.post(`/team.update`, params);
|
||||||
invariant(res?.data, "Team response not available");
|
invariant(res?.data, "Team response not available");
|
||||||
runInAction("AuthStore#updateTeam", () => {
|
this.team?.updateFromJson(res.data);
|
||||||
this.addPolicies(res.policies);
|
this.addPolicies(res.policies);
|
||||||
this.team = new Team(res.data, this);
|
} catch (err) {
|
||||||
});
|
this.team?.updateFromJson(previousData);
|
||||||
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
this.isSaving = false;
|
this.isSaving = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ async function teamCreator({
|
|||||||
// one via ClearBit, or fallback to colored initials in worst case scenario
|
// one via ClearBit, or fallback to colored initials in worst case scenario
|
||||||
if (!avatarUrl || !avatarUrl.startsWith("http")) {
|
if (!avatarUrl || !avatarUrl.startsWith("http")) {
|
||||||
avatarUrl = await generateAvatarUrl({
|
avatarUrl = await generateAvatarUrl({
|
||||||
name,
|
|
||||||
domain,
|
domain,
|
||||||
id: subdomain,
|
id: subdomain,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -329,13 +329,6 @@ export class Environment {
|
|||||||
*/
|
*/
|
||||||
public RELEASE = this.toOptionalString(process.env.RELEASE);
|
public RELEASE = this.toOptionalString(process.env.RELEASE);
|
||||||
|
|
||||||
/**
|
|
||||||
* An optional host from which to load default avatars.
|
|
||||||
*/
|
|
||||||
@IsUrl()
|
|
||||||
public DEFAULT_AVATAR_HOST =
|
|
||||||
process.env.DEFAULT_AVATAR_HOST ?? "https://tiley.herokuapp.com";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Google Analytics tracking ID, only v3 supported at this time.
|
* A Google Analytics tracking ID, only v3 supported at this time.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import { CollectionPermission, TeamPreference } from "@shared/types";
|
|||||||
import { getBaseDomain, RESERVED_SUBDOMAINS } from "@shared/utils/domains";
|
import { getBaseDomain, RESERVED_SUBDOMAINS } from "@shared/utils/domains";
|
||||||
import env from "@server/env";
|
import env from "@server/env";
|
||||||
import DeleteAttachmentTask from "@server/queues/tasks/DeleteAttachmentTask";
|
import DeleteAttachmentTask from "@server/queues/tasks/DeleteAttachmentTask";
|
||||||
import { generateAvatarUrl } from "@server/utils/avatars";
|
|
||||||
import parseAttachmentIds from "@server/utils/parseAttachmentIds";
|
import parseAttachmentIds from "@server/utils/parseAttachmentIds";
|
||||||
import Attachment from "./Attachment";
|
import Attachment from "./Attachment";
|
||||||
import AuthenticationProvider from "./AuthenticationProvider";
|
import AuthenticationProvider from "./AuthenticationProvider";
|
||||||
@@ -94,8 +93,20 @@ class Team extends ParanoidModel {
|
|||||||
@AllowNull
|
@AllowNull
|
||||||
@IsUrl
|
@IsUrl
|
||||||
@Length({ max: 4096, msg: "avatarUrl must be 4096 characters or less" })
|
@Length({ max: 4096, msg: "avatarUrl must be 4096 characters or less" })
|
||||||
@Column
|
@Column(DataType.STRING)
|
||||||
avatarUrl: string | null;
|
get avatarUrl() {
|
||||||
|
const original = this.getDataValue("avatarUrl");
|
||||||
|
|
||||||
|
if (original && !original.startsWith("https://tiley.herokuapp.com")) {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
set avatarUrl(value: string | null) {
|
||||||
|
this.setDataValue("avatarUrl", value);
|
||||||
|
}
|
||||||
|
|
||||||
@Default(true)
|
@Default(true)
|
||||||
@Column
|
@Column
|
||||||
@@ -163,16 +174,6 @@ class Team extends ParanoidModel {
|
|||||||
return url.href.replace(/\/$/, "");
|
return url.href.replace(/\/$/, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
get logoUrl() {
|
|
||||||
return (
|
|
||||||
this.avatarUrl ||
|
|
||||||
generateAvatarUrl({
|
|
||||||
id: this.id,
|
|
||||||
name: this.name,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preferences that decide behavior for the team.
|
* Preferences that decide behavior for the team.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -180,17 +180,11 @@ class User extends ParanoidModel {
|
|||||||
get avatarUrl() {
|
get avatarUrl() {
|
||||||
const original = this.getDataValue("avatarUrl");
|
const original = this.getDataValue("avatarUrl");
|
||||||
|
|
||||||
if (original) {
|
if (original && !original.startsWith("https://tiley.herokuapp.com")) {
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
const color = this.color.replace(/^#/, "");
|
return null;
|
||||||
const initial = this.name ? this.name[0] : "?";
|
|
||||||
const hash = crypto
|
|
||||||
.createHash("md5")
|
|
||||||
.update(this.email || "")
|
|
||||||
.digest("hex");
|
|
||||||
return `${env.DEFAULT_AVATAR_HOST}/avatar/${hash}/${initial}.png?c=${color}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set avatarUrl(value: string | null) {
|
set avatarUrl(value: string | null) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
exports[`presents a user 1`] = `
|
exports[`presents a user 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"avatarUrl": "https://tiley.herokuapp.com/avatar/d41d8cd98f00b204e9800998ecf8427e/T.png?c=FF5C80",
|
"avatarUrl": null,
|
||||||
"color": "#FF5C80",
|
"color": "#FF5C80",
|
||||||
"createdAt": undefined,
|
"createdAt": undefined,
|
||||||
"id": "123",
|
"id": "123",
|
||||||
@@ -17,7 +17,7 @@ Object {
|
|||||||
|
|
||||||
exports[`presents a user without slack data 1`] = `
|
exports[`presents a user without slack data 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"avatarUrl": "https://tiley.herokuapp.com/avatar/d41d8cd98f00b204e9800998ecf8427e/T.png?c=FF5C80",
|
"avatarUrl": null,
|
||||||
"color": "#FF5C80",
|
"color": "#FF5C80",
|
||||||
"createdAt": undefined,
|
"createdAt": undefined,
|
||||||
"id": "123",
|
"id": "123",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export default function present(team: Team, isSignedIn = false) {
|
|||||||
return {
|
return {
|
||||||
id: team.id,
|
id: team.id,
|
||||||
name: team.name,
|
name: team.name,
|
||||||
avatarUrl: team.logoUrl,
|
avatarUrl: team.avatarUrl,
|
||||||
url: team.url,
|
url: team.url,
|
||||||
isSignedIn,
|
isSignedIn,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export default function present(team: Team) {
|
|||||||
return {
|
return {
|
||||||
id: team.id,
|
id: team.id,
|
||||||
name: team.name,
|
name: team.name,
|
||||||
avatarUrl: team.logoUrl,
|
avatarUrl: team.avatarUrl,
|
||||||
sharing: team.sharing,
|
sharing: team.sharing,
|
||||||
memberCollectionCreate: team.memberCollectionCreate,
|
memberCollectionCreate: team.memberCollectionCreate,
|
||||||
collaborativeEditing: team.collaborativeEditing,
|
collaborativeEditing: team.collaborativeEditing,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
exports[`#users.activate should activate a suspended user 1`] = `
|
exports[`#users.activate should activate a suspended user 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"data": Object {
|
"data": Object {
|
||||||
"avatarUrl": "https://tiley.herokuapp.com/avatar/111d68d06e2d317b5a59c2c6c5bad808/U.png?c=e600e0",
|
"avatarUrl": null,
|
||||||
"color": "#e600e0",
|
"color": "#e600e0",
|
||||||
"createdAt": "2018-01-02T00:00:00.000Z",
|
"createdAt": "2018-01-02T00:00:00.000Z",
|
||||||
"email": "user1@example.com",
|
"email": "user1@example.com",
|
||||||
@@ -59,7 +59,7 @@ Object {
|
|||||||
exports[`#users.demote should demote an admin 1`] = `
|
exports[`#users.demote should demote an admin 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"data": Object {
|
"data": Object {
|
||||||
"avatarUrl": "https://tiley.herokuapp.com/avatar/111d68d06e2d317b5a59c2c6c5bad808/U.png?c=e600e0",
|
"avatarUrl": null,
|
||||||
"color": "#e600e0",
|
"color": "#e600e0",
|
||||||
"createdAt": "2018-01-02T00:00:00.000Z",
|
"createdAt": "2018-01-02T00:00:00.000Z",
|
||||||
"email": "user1@example.com",
|
"email": "user1@example.com",
|
||||||
@@ -97,7 +97,7 @@ Object {
|
|||||||
exports[`#users.demote should demote an admin to member 1`] = `
|
exports[`#users.demote should demote an admin to member 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"data": Object {
|
"data": Object {
|
||||||
"avatarUrl": "https://tiley.herokuapp.com/avatar/111d68d06e2d317b5a59c2c6c5bad808/U.png?c=e600e0",
|
"avatarUrl": null,
|
||||||
"color": "#e600e0",
|
"color": "#e600e0",
|
||||||
"createdAt": "2018-01-02T00:00:00.000Z",
|
"createdAt": "2018-01-02T00:00:00.000Z",
|
||||||
"email": "user1@example.com",
|
"email": "user1@example.com",
|
||||||
@@ -135,7 +135,7 @@ Object {
|
|||||||
exports[`#users.demote should demote an admin to viewer 1`] = `
|
exports[`#users.demote should demote an admin to viewer 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"data": Object {
|
"data": Object {
|
||||||
"avatarUrl": "https://tiley.herokuapp.com/avatar/111d68d06e2d317b5a59c2c6c5bad808/U.png?c=e600e0",
|
"avatarUrl": null,
|
||||||
"color": "#e600e0",
|
"color": "#e600e0",
|
||||||
"createdAt": "2018-01-02T00:00:00.000Z",
|
"createdAt": "2018-01-02T00:00:00.000Z",
|
||||||
"email": "user1@example.com",
|
"email": "user1@example.com",
|
||||||
@@ -191,7 +191,7 @@ Object {
|
|||||||
exports[`#users.promote should promote a new admin 1`] = `
|
exports[`#users.promote should promote a new admin 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"data": Object {
|
"data": Object {
|
||||||
"avatarUrl": "https://tiley.herokuapp.com/avatar/111d68d06e2d317b5a59c2c6c5bad808/U.png?c=e600e0",
|
"avatarUrl": null,
|
||||||
"color": "#e600e0",
|
"color": "#e600e0",
|
||||||
"createdAt": "2018-01-02T00:00:00.000Z",
|
"createdAt": "2018-01-02T00:00:00.000Z",
|
||||||
"email": "user1@example.com",
|
"email": "user1@example.com",
|
||||||
@@ -256,7 +256,7 @@ Object {
|
|||||||
exports[`#users.suspend should suspend an user 1`] = `
|
exports[`#users.suspend should suspend an user 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"data": Object {
|
"data": Object {
|
||||||
"avatarUrl": "https://tiley.herokuapp.com/avatar/111d68d06e2d317b5a59c2c6c5bad808/U.png?c=e600e0",
|
"avatarUrl": null,
|
||||||
"color": "#e600e0",
|
"color": "#e600e0",
|
||||||
"createdAt": "2018-01-02T00:00:00.000Z",
|
"createdAt": "2018-01-02T00:00:00.000Z",
|
||||||
"email": "user1@example.com",
|
"email": "user1@example.com",
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export async function signIn(
|
|||||||
...existing,
|
...existing,
|
||||||
[team.id]: {
|
[team.id]: {
|
||||||
name: team.name,
|
name: team.name,
|
||||||
logoUrl: team.logoUrl,
|
logoUrl: team.avatarUrl,
|
||||||
url: team.url,
|
url: team.url,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,43 +4,6 @@ it("should return clearbit url if available", async () => {
|
|||||||
const url = await generateAvatarUrl({
|
const url = await generateAvatarUrl({
|
||||||
id: "google",
|
id: "google",
|
||||||
domain: "google.com",
|
domain: "google.com",
|
||||||
name: "Google",
|
|
||||||
});
|
});
|
||||||
expect(url).toBe("https://logo.clearbit.com/google.com");
|
expect(url).toBe("https://logo.clearbit.com/google.com");
|
||||||
});
|
});
|
||||||
it("should return tiley url if clearbit unavailable", async () => {
|
|
||||||
const url = await generateAvatarUrl({
|
|
||||||
id: "invalid",
|
|
||||||
domain: "example.invalid",
|
|
||||||
name: "Invalid",
|
|
||||||
});
|
|
||||||
expect(url).toBe(
|
|
||||||
"https://tiley.herokuapp.com/avatar/f1234d75178d892a133a410355a5a990cf75d2f33eba25d575943d4df632f3a4/I.png"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
it("should return tiley url if domain not provided", async () => {
|
|
||||||
const url = await generateAvatarUrl({
|
|
||||||
id: "google",
|
|
||||||
name: "Google",
|
|
||||||
});
|
|
||||||
expect(url).toBe(
|
|
||||||
"https://tiley.herokuapp.com/avatar/bbdefa2950f49882f295b1285d4fa9dec45fc4144bfb07ee6acc68762d12c2e3/G.png"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
it("should return tiley url if name not provided", async () => {
|
|
||||||
const url = await generateAvatarUrl({
|
|
||||||
id: "google",
|
|
||||||
});
|
|
||||||
expect(url).toBe(
|
|
||||||
"https://tiley.herokuapp.com/avatar/bbdefa2950f49882f295b1285d4fa9dec45fc4144bfb07ee6acc68762d12c2e3/U.png"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
it("should return tiley url with encoded name", async () => {
|
|
||||||
const url = await generateAvatarUrl({
|
|
||||||
id: "google",
|
|
||||||
name: "株",
|
|
||||||
});
|
|
||||||
expect(url).toBe(
|
|
||||||
"https://tiley.herokuapp.com/avatar/bbdefa2950f49882f295b1285d4fa9dec45fc4144bfb07ee6acc68762d12c2e3/%E6%A0%AA.png"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,21 +1,17 @@
|
|||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import fetch from "fetch-with-proxy";
|
import fetch from "fetch-with-proxy";
|
||||||
import env from "@server/env";
|
|
||||||
|
|
||||||
export async function generateAvatarUrl({
|
export async function generateAvatarUrl({
|
||||||
id,
|
id,
|
||||||
domain,
|
domain,
|
||||||
name = "Unknown",
|
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
domain?: string;
|
domain?: string;
|
||||||
name?: string;
|
|
||||||
}) {
|
}) {
|
||||||
// attempt to get logo from Clearbit API. If one doesn't exist then
|
// attempt to get logo from Clearbit API. If one doesn't exist then
|
||||||
// fall back to using tiley to generate a placeholder logo
|
// fall back to using tiley to generate a placeholder logo
|
||||||
const hash = crypto.createHash("sha256");
|
const hash = crypto.createHash("sha256");
|
||||||
hash.update(id);
|
hash.update(id);
|
||||||
const hashedId = hash.digest("hex");
|
|
||||||
let cbResponse, cbUrl;
|
let cbResponse, cbUrl;
|
||||||
|
|
||||||
if (domain) {
|
if (domain) {
|
||||||
@@ -28,8 +24,5 @@ export async function generateAvatarUrl({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const tileyUrl = `${
|
return cbUrl && cbResponse && cbResponse.status === 200 ? cbUrl : null;
|
||||||
env.DEFAULT_AVATAR_HOST
|
|
||||||
}/avatar/${hashedId}/${encodeURIComponent(name[0])}.png`;
|
|
||||||
return cbUrl && cbResponse && cbResponse.status === 200 ? cbUrl : tileyUrl;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import fetch from "fetch-with-proxy";
|
|||||||
import { compact } from "lodash";
|
import { compact } from "lodash";
|
||||||
import { useAgent } from "request-filtering-agent";
|
import { useAgent } from "request-filtering-agent";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import env from "@server/env";
|
|
||||||
import Logger from "@server/logging/Logger";
|
import Logger from "@server/logging/Logger";
|
||||||
|
|
||||||
const AWS_S3_ACCELERATE_URL = process.env.AWS_S3_ACCELERATE_URL;
|
const AWS_S3_ACCELERATE_URL = process.env.AWS_S3_ACCELERATE_URL;
|
||||||
@@ -184,11 +183,7 @@ export const uploadToS3FromUrl = async (
|
|||||||
acl: string
|
acl: string
|
||||||
) => {
|
) => {
|
||||||
const endpoint = publicS3Endpoint(true);
|
const endpoint = publicS3Endpoint(true);
|
||||||
if (
|
if (url.startsWith("/api") || url.startsWith(endpoint)) {
|
||||||
url.startsWith("/api") ||
|
|
||||||
url.startsWith(endpoint) ||
|
|
||||||
url.startsWith(env.DEFAULT_AVATAR_HOST)
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user