mirror of
https://github.com/docmost/docmost.git
synced 2025-11-20 09:51:12 +10:00
Editor link components
* other minor fixes
This commit is contained in:
@ -0,0 +1,34 @@
|
||||
import React from "react";
|
||||
import { Button, Group, TextInput } from "@mantine/core";
|
||||
import { IconLink } from "@tabler/icons-react";
|
||||
import { useLinkEditorState } from "@/features/editor/components/link/use-link-editor-state.tsx";
|
||||
import { LinkEditorPanelProps } from "@/features/editor/components/link/types.ts";
|
||||
|
||||
export const LinkEditorPanel = ({
|
||||
onSetLink,
|
||||
initialUrl,
|
||||
}: LinkEditorPanelProps) => {
|
||||
const state = useLinkEditorState({
|
||||
onSetLink,
|
||||
initialUrl,
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={state.handleSubmit}>
|
||||
<Group gap="xs" style={{ flex: 1 }} wrap="nowrap">
|
||||
<TextInput
|
||||
leftSection={<IconLink size={16} />}
|
||||
variant="filled"
|
||||
placeholder="Paste link"
|
||||
value={state.url}
|
||||
onChange={state.onChange}
|
||||
/>
|
||||
<Button p={"xs"} type="submit" disabled={!state.isValidUrl}>
|
||||
Save
|
||||
</Button>
|
||||
</Group>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,86 @@
|
||||
import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { EditorMenuProps } from "@/features/editor/components/table/types/types.ts";
|
||||
import { LinkEditorPanel } from "@/features/editor/components/link/link-editor-panel.tsx";
|
||||
import { LinkPreviewPanel } from "@/features/editor/components/link/link-preview.tsx";
|
||||
import { Card } from "@mantine/core";
|
||||
|
||||
export function LinkMenu({ editor, appendTo }: EditorMenuProps) {
|
||||
const [showEdit, setShowEdit] = useState(false);
|
||||
|
||||
const shouldShow = useCallback(() => {
|
||||
return editor.isActive("link");
|
||||
}, [editor]);
|
||||
|
||||
const { href: link } = editor.getAttributes("link");
|
||||
|
||||
const handleEdit = useCallback(() => {
|
||||
setShowEdit(true);
|
||||
}, []);
|
||||
|
||||
const onSetLink = useCallback(
|
||||
(url: string) => {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.extendMarkRange("link")
|
||||
.setLink({ href: url })
|
||||
.run();
|
||||
setShowEdit(false);
|
||||
},
|
||||
[editor],
|
||||
);
|
||||
|
||||
const onUnsetLink = useCallback(() => {
|
||||
editor.chain().focus().extendMarkRange("link").unsetLink().run();
|
||||
setShowEdit(false);
|
||||
return null;
|
||||
}, [editor]);
|
||||
|
||||
const onShowEdit = useCallback(() => {
|
||||
setShowEdit(true);
|
||||
}, []);
|
||||
|
||||
const onHideEdit = useCallback(() => {
|
||||
setShowEdit(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<BaseBubbleMenu
|
||||
editor={editor}
|
||||
pluginKey={`link-menu}`}
|
||||
updateDelay={0}
|
||||
tippyOptions={{
|
||||
appendTo: () => {
|
||||
return appendTo?.current;
|
||||
},
|
||||
onHidden: () => {
|
||||
setShowEdit(false);
|
||||
},
|
||||
placement: "bottom",
|
||||
offset: [0, 5],
|
||||
zIndex: 101,
|
||||
}}
|
||||
shouldShow={shouldShow}
|
||||
>
|
||||
{showEdit ? (
|
||||
<Card
|
||||
withBorder
|
||||
radius="md"
|
||||
padding="xs"
|
||||
bg="var(--mantine-color-body)"
|
||||
>
|
||||
<LinkEditorPanel initialUrl={link} onSetLink={onSetLink} />
|
||||
</Card>
|
||||
) : (
|
||||
<LinkPreviewPanel
|
||||
url={link}
|
||||
onClear={onUnsetLink}
|
||||
onEdit={handleEdit}
|
||||
/>
|
||||
)}
|
||||
</BaseBubbleMenu>
|
||||
);
|
||||
}
|
||||
|
||||
export default LinkMenu;
|
||||
@ -0,0 +1,61 @@
|
||||
import {
|
||||
Tooltip,
|
||||
ActionIcon,
|
||||
Card,
|
||||
Divider,
|
||||
Anchor,
|
||||
Flex,
|
||||
} from "@mantine/core";
|
||||
import { IconLinkOff, IconPencil } from "@tabler/icons-react";
|
||||
|
||||
export type LinkPreviewPanelProps = {
|
||||
url: string;
|
||||
onEdit: () => void;
|
||||
onClear: () => void;
|
||||
};
|
||||
|
||||
export const LinkPreviewPanel = ({
|
||||
onClear,
|
||||
onEdit,
|
||||
url,
|
||||
}: LinkPreviewPanelProps) => {
|
||||
return (
|
||||
<>
|
||||
<Card withBorder radius="md" padding="xs" bg="var(--mantine-color-body)">
|
||||
<Flex align="center">
|
||||
<Tooltip label={url}>
|
||||
<Anchor
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
inherit
|
||||
style={{
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{url}
|
||||
</Anchor>
|
||||
</Tooltip>
|
||||
|
||||
<Flex align="center">
|
||||
<Divider mx={4} orientation="vertical" />
|
||||
|
||||
<Tooltip label="Edit link">
|
||||
<ActionIcon onClick={onEdit} variant="subtle" color="gray">
|
||||
<IconPencil size={16} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label="Remove link">
|
||||
<ActionIcon onClick={onClear} variant="subtle" color="red">
|
||||
<IconLinkOff size={16} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
};
|
||||
4
apps/client/src/features/editor/components/link/types.ts
Normal file
4
apps/client/src/features/editor/components/link/types.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export type LinkEditorPanelProps = {
|
||||
initialUrl?: string;
|
||||
onSetLink: (url: string, openInNewTab?: boolean) => void;
|
||||
};
|
||||
@ -0,0 +1,33 @@
|
||||
import React, { useCallback, useMemo, useState } from "react";
|
||||
import { LinkEditorPanelProps } from "@/features/editor/components/link/types.ts";
|
||||
|
||||
export const useLinkEditorState = ({
|
||||
initialUrl,
|
||||
onSetLink,
|
||||
}: LinkEditorPanelProps) => {
|
||||
const [url, setUrl] = useState(initialUrl || "");
|
||||
|
||||
const onChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setUrl(event.target.value);
|
||||
}, []);
|
||||
|
||||
const isValidUrl = useMemo(() => /^(\S+):(\/\/)?\S+$/.test(url), [url]);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (isValidUrl) {
|
||||
onSetLink(url);
|
||||
}
|
||||
},
|
||||
[url, isValidUrl, onSetLink],
|
||||
);
|
||||
|
||||
return {
|
||||
url,
|
||||
setUrl,
|
||||
onChange,
|
||||
handleSubmit,
|
||||
isValidUrl,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user