mirror of
https://github.com/outline/outline.git
synced 2026-08-03 13:27:25 +03:00
* chore: Exhaustive deps turned to error * Refactor useDragResize * Refactor Lightbox to explicit state machine * feedback * ci
141 lines
3.2 KiB
TypeScript
141 lines
3.2 KiB
TypeScript
import * as React from "react";
|
|
import styled from "styled-components";
|
|
import type { EmbedDescriptor } from "../embeds";
|
|
import { getMatchingEmbed } from "../lib/embeds";
|
|
import type { ComponentProps } from "../types";
|
|
import DisabledEmbed from "./DisabledEmbed";
|
|
import Frame from "./Frame";
|
|
import { ResizeBottom, ResizeLeft, ResizeRight } from "./ResizeHandle";
|
|
import useDragResize from "./hooks/useDragResize";
|
|
|
|
type Props = ComponentProps & {
|
|
embeds: EmbedDescriptor[];
|
|
embedsDisabled?: boolean;
|
|
style?: React.CSSProperties;
|
|
onChangeSize?: (props: { width: number; height?: number }) => void;
|
|
};
|
|
|
|
const Embed = (props: Props) => {
|
|
const ref = React.useRef<HTMLDivElement>(null);
|
|
const { node, isEditable, embedsDisabled, onChangeSize } = props;
|
|
const naturalWidth = 0;
|
|
const naturalHeight = 400;
|
|
const isResizable = !!onChangeSize && !embedsDisabled;
|
|
|
|
const { width, height, handlePointerDown, dragging } = useDragResize({
|
|
width: node.attrs.width ?? naturalWidth,
|
|
height: node.attrs.height ?? naturalHeight,
|
|
naturalWidth,
|
|
naturalHeight,
|
|
onChangeSize,
|
|
ref,
|
|
});
|
|
|
|
const style: React.CSSProperties = {
|
|
width: width || "100%",
|
|
height: height || 400,
|
|
maxWidth: "100%",
|
|
pointerEvents: dragging ? "none" : "all",
|
|
};
|
|
|
|
return (
|
|
<FrameWrapper ref={ref} $dragging={!!dragging}>
|
|
<InnerEmbed style={style} {...props} />
|
|
{isEditable && isResizable && (
|
|
<>
|
|
<ResizeBottom
|
|
onPointerDown={handlePointerDown("bottom")}
|
|
$dragging={!!dragging}
|
|
/>
|
|
</>
|
|
)}
|
|
</FrameWrapper>
|
|
);
|
|
};
|
|
|
|
function InnerEmbed({
|
|
isEditable,
|
|
isSelected,
|
|
node,
|
|
embeds,
|
|
embedsDisabled,
|
|
style,
|
|
}: Props) {
|
|
const cache = React.useMemo(
|
|
() => getMatchingEmbed(embeds, node.attrs.href),
|
|
[embeds, node.attrs.href]
|
|
);
|
|
|
|
if (!cache) {
|
|
return null;
|
|
}
|
|
|
|
const { embed, matches } = cache;
|
|
|
|
if (embedsDisabled) {
|
|
return (
|
|
<DisabledEmbed
|
|
href={node.attrs.href}
|
|
embed={embed}
|
|
isEditable={isEditable}
|
|
isSelected={isSelected}
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (embed.transformMatch) {
|
|
const src = embed.transformMatch(matches);
|
|
return (
|
|
<Frame
|
|
src={src}
|
|
style={style}
|
|
isSelected={isSelected}
|
|
canonicalUrl={embed.hideToolbar ? undefined : node.attrs.href}
|
|
title={embed.title}
|
|
referrerPolicy="strict-origin-when-cross-origin"
|
|
border
|
|
/>
|
|
);
|
|
}
|
|
|
|
if ("component" in embed) {
|
|
return (
|
|
// @ts-expect-error Component type
|
|
<embed.component
|
|
attrs={node.attrs}
|
|
style={style}
|
|
matches={matches}
|
|
isEditable={isEditable}
|
|
isSelected={isSelected}
|
|
embed={embed}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
const FrameWrapper = styled.div<{ $dragging: boolean }>`
|
|
line-height: 0;
|
|
position: relative;
|
|
margin-left: auto;
|
|
margin-right: auto;
|
|
white-space: nowrap;
|
|
cursor: default;
|
|
border-radius: 8px;
|
|
user-select: none;
|
|
max-width: 100%;
|
|
|
|
transition-property: width, max-height;
|
|
transition-duration: ${(props) => (props.$dragging ? "0ms" : "150ms")};
|
|
transition-timing-function: ease-in-out;
|
|
|
|
&:hover {
|
|
${ResizeLeft}, ${ResizeRight} {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
`;
|
|
|
|
export default Embed;
|