mirror of
https://github.com/outline/outline.git
synced 2026-06-13 03:14:59 +03:00
fix: More selection toolbar fixes around link selection (#11408)
This commit is contained in:
@@ -95,7 +95,7 @@ const IconWrapper = styled.span`
|
||||
export const Outline = styled(Flex)<{
|
||||
margin?: string | number;
|
||||
hasError?: boolean;
|
||||
focused?: boolean;
|
||||
$focused?: boolean;
|
||||
}>`
|
||||
flex: 1;
|
||||
margin: ${(props) =>
|
||||
@@ -106,7 +106,7 @@ export const Outline = styled(Flex)<{
|
||||
border-color: ${(props) =>
|
||||
props.hasError
|
||||
? props.theme.danger
|
||||
: props.focused
|
||||
: props.$focused
|
||||
? props.theme.inputBorderFocused
|
||||
: props.theme.inputBorder};
|
||||
border-radius: 4px;
|
||||
@@ -224,7 +224,7 @@ function Input(
|
||||
) : (
|
||||
wrappedLabel
|
||||
))}
|
||||
<Outline focused={focused} margin={margin}>
|
||||
<Outline $focused={focused} margin={margin}>
|
||||
{prefix}
|
||||
{icon && <IconWrapper>{icon}</IconWrapper>}
|
||||
{type === "textarea" ? (
|
||||
|
||||
@@ -33,6 +33,7 @@ type Props = {
|
||||
mark?: Mark;
|
||||
dictionary: Dictionary;
|
||||
view: EditorView;
|
||||
autoFocus?: boolean;
|
||||
onLinkAdd: () => void;
|
||||
onLinkUpdate: () => void;
|
||||
onLinkRemove: () => void;
|
||||
@@ -45,6 +46,7 @@ const LinkEditor: React.FC<Props> = ({
|
||||
mark,
|
||||
dictionary,
|
||||
view,
|
||||
autoFocus,
|
||||
onLinkAdd,
|
||||
onLinkUpdate,
|
||||
onLinkRemove,
|
||||
@@ -209,7 +211,7 @@ const LinkEditor: React.FC<Props> = ({
|
||||
onKeyDown={handleKeyDown}
|
||||
onChange={handleSearch}
|
||||
onFocus={handleSearch}
|
||||
autoFocus={getHref() === ""}
|
||||
autoFocus={autoFocus}
|
||||
readOnly={!view.editable}
|
||||
/>
|
||||
{actions.map((action, index) => {
|
||||
|
||||
@@ -87,25 +87,29 @@ export function SelectionToolbar(props: Props) {
|
||||
const isMobile = useMobile();
|
||||
const isActive = props.isActive || isMobile;
|
||||
const { state } = view;
|
||||
const [autoFocusLinkInput, setAutoFocusLinkInput] = React.useState(false);
|
||||
const isDragging = useIsDragging(state);
|
||||
const { selection } = state;
|
||||
const [activeToolbar, setActiveToolbar] = React.useState<Toolbar | null>(
|
||||
null
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const { selection } = state;
|
||||
const linkMark =
|
||||
selection instanceof NodeSelection
|
||||
? getMarkRangeNodeSelection(selection, state.schema.marks.link)
|
||||
: getMarkRange(selection.$from, state.schema.marks.link);
|
||||
const linkMark =
|
||||
selection instanceof NodeSelection
|
||||
? getMarkRangeNodeSelection(selection, state.schema.marks.link)
|
||||
: getMarkRange(selection.$from, state.schema.marks.link);
|
||||
|
||||
const isEmbedSelection =
|
||||
selection instanceof NodeSelection &&
|
||||
selection.node.type.name === "embed";
|
||||
const isEmbedSelection =
|
||||
selection instanceof NodeSelection && selection.node.type.name === "embed";
|
||||
|
||||
const isCodeSelection = isInCode(state, { onlyBlock: true });
|
||||
const isNoticeSelection = isInNotice(state);
|
||||
const isCodeSelection = isInCode(state, { onlyBlock: true });
|
||||
const isNoticeSelection = isInNotice(state);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
if (!isActive) {
|
||||
setActiveToolbar(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEmbedSelection && !readOnly) {
|
||||
setActiveToolbar(Toolbar.Media);
|
||||
@@ -124,22 +128,37 @@ export function SelectionToolbar(props: Props) {
|
||||
} else if (selection.empty) {
|
||||
setActiveToolbar(null);
|
||||
}
|
||||
}, [readOnly, selection]);
|
||||
}, [
|
||||
readOnly,
|
||||
isActive,
|
||||
selection,
|
||||
linkMark,
|
||||
isEmbedSelection,
|
||||
isCodeSelection,
|
||||
isNoticeSelection,
|
||||
]);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
if (autoFocusLinkInput && activeToolbar !== Toolbar.Link) {
|
||||
setAutoFocusLinkInput(false);
|
||||
}
|
||||
}, [activeToolbar]);
|
||||
|
||||
// Refocus the editor when the link toolbar closes to prevent focus loss
|
||||
const prevActiveToolbar = React.useRef(activeToolbar);
|
||||
React.useEffect(() => {
|
||||
React.useLayoutEffect(() => {
|
||||
if (
|
||||
prevActiveToolbar.current === Toolbar.Link &&
|
||||
activeToolbar !== Toolbar.Link &&
|
||||
!readOnly
|
||||
!readOnly &&
|
||||
isActive
|
||||
) {
|
||||
view.focus();
|
||||
}
|
||||
prevActiveToolbar.current = activeToolbar;
|
||||
}, [activeToolbar, readOnly, view]);
|
||||
}, [activeToolbar, readOnly, isActive, view]);
|
||||
|
||||
React.useEffect(() => {
|
||||
React.useLayoutEffect(() => {
|
||||
const handleClickOutside = (ev: MouseEvent): void => {
|
||||
if (
|
||||
ev.target instanceof HTMLElement &&
|
||||
@@ -193,11 +212,10 @@ export function SelectionToolbar(props: Props) {
|
||||
) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
if (activeToolbar === Toolbar.Link) {
|
||||
setActiveToolbar(Toolbar.Menu);
|
||||
} else if (activeToolbar === Toolbar.Menu) {
|
||||
setActiveToolbar(Toolbar.Link);
|
||||
}
|
||||
setAutoFocusLinkInput(true);
|
||||
setActiveToolbar(
|
||||
activeToolbar === Toolbar.Link ? Toolbar.Menu : Toolbar.Link
|
||||
);
|
||||
}
|
||||
},
|
||||
view.dom,
|
||||
@@ -218,12 +236,6 @@ export function SelectionToolbar(props: Props) {
|
||||
const isAttachmentSelection =
|
||||
selection instanceof NodeSelection &&
|
||||
selection.node.type.name === "attachment";
|
||||
const isCodeSelection = isInCode(state, { onlyBlock: true });
|
||||
const isNoticeSelection = isInNotice(state);
|
||||
const link =
|
||||
selection instanceof NodeSelection
|
||||
? getMarkRangeNodeSelection(selection, state.schema.marks.link)
|
||||
: getMarkRange(selection.$from, state.schema.marks.link);
|
||||
|
||||
let items: MenuItem[] = [];
|
||||
let align: "center" | "start" | "end" = "center";
|
||||
@@ -289,6 +301,7 @@ export function SelectionToolbar(props: Props) {
|
||||
|
||||
if (item.name === "linkOnImage" || item.name === "addLink") {
|
||||
item.onClick = () => {
|
||||
setAutoFocusLinkInput(true);
|
||||
setActiveToolbar(Toolbar.Link);
|
||||
};
|
||||
}
|
||||
@@ -315,10 +328,11 @@ export function SelectionToolbar(props: Props) {
|
||||
>
|
||||
{activeToolbar === Toolbar.Link ? (
|
||||
<LinkEditor
|
||||
key={`${selection.from}-${selection.to}`}
|
||||
key={`link-${selection.anchor}`}
|
||||
dictionary={dictionary}
|
||||
autoFocus={autoFocusLinkInput}
|
||||
view={view}
|
||||
mark={link ? link.mark : undefined}
|
||||
mark={linkMark ? linkMark.mark : undefined}
|
||||
onLinkAdd={() => setActiveToolbar(null)}
|
||||
onLinkUpdate={() => setActiveToolbar(null)}
|
||||
onLinkRemove={() => setActiveToolbar(null)}
|
||||
@@ -328,7 +342,7 @@ export function SelectionToolbar(props: Props) {
|
||||
/>
|
||||
) : activeToolbar === Toolbar.Media ? (
|
||||
<MediaLinkEditor
|
||||
key={`embed-${selection.from}`}
|
||||
key={`embed-${selection.anchor}`}
|
||||
node={
|
||||
"node" in selection ? (selection as NodeSelection).node : undefined
|
||||
}
|
||||
|
||||
@@ -887,7 +887,7 @@ export class Editor extends React.PureComponent<
|
||||
images={this.getLightboxImages()}
|
||||
activeImage={this.state.activeLightboxImage}
|
||||
onUpdate={this.updateActiveLightboxImage}
|
||||
onClose={this.view.focus}
|
||||
onClose={this.view.focus.bind(this.view)}
|
||||
/>
|
||||
)}
|
||||
</EditorContext.Provider>
|
||||
|
||||
Reference in New Issue
Block a user