mirror of
https://github.com/docmost/docmost.git
synced 2026-06-11 10:46:54 +08:00
feat: editor inline status node (#1973)
* inline status node * fix alignment * fix * typed storage * fix math block popup on select all
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { NodeViewProps, NodeViewWrapper } from "@tiptap/react";
|
||||
import { Popover, TextInput, Group, Box } from "@mantine/core";
|
||||
import { useDebouncedCallback } from "@mantine/hooks";
|
||||
import { IconCheck } from "@tabler/icons-react";
|
||||
import clsx from "clsx";
|
||||
import classes from "./status.module.css";
|
||||
import type { StatusColor } from "@docmost/editor-ext";
|
||||
|
||||
const STATUS_COLORS: { name: StatusColor; bg: string }[] = [
|
||||
{ name: "gray", bg: "var(--mantine-color-gray-4)" },
|
||||
{ name: "blue", bg: "var(--mantine-color-blue-4)" },
|
||||
{ name: "green", bg: "var(--mantine-color-green-4)" },
|
||||
{ name: "yellow", bg: "var(--mantine-color-yellow-4)" },
|
||||
{ name: "red", bg: "var(--mantine-color-red-4)" },
|
||||
{ name: "purple", bg: "var(--mantine-color-violet-4)" },
|
||||
];
|
||||
|
||||
const colorClassMap: Record<StatusColor, string> = {
|
||||
gray: classes.colorGray,
|
||||
blue: classes.colorBlue,
|
||||
green: classes.colorGreen,
|
||||
yellow: classes.colorYellow,
|
||||
red: classes.colorRed,
|
||||
purple: classes.colorPurple,
|
||||
};
|
||||
|
||||
export default function StatusView(props: NodeViewProps) {
|
||||
const { node, updateAttributes, deleteNode, editor } = props;
|
||||
const { text, color } = node.attrs as {
|
||||
text: string;
|
||||
color: StatusColor;
|
||||
};
|
||||
|
||||
const [opened, setOpened] = useState(false);
|
||||
const [inputValue, setInputValue] = useState(text);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const storage = editor.storage?.status;
|
||||
if (storage?.autoOpen) {
|
||||
storage.autoOpen = false;
|
||||
setOpened(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (opened) {
|
||||
setInputValue(text);
|
||||
setTimeout(() => inputRef.current?.focus(), 0);
|
||||
}
|
||||
}, [opened]);
|
||||
|
||||
const debouncedUpdateAttributes = useDebouncedCallback(
|
||||
(val: string) => updateAttributes({ text: val }),
|
||||
100,
|
||||
);
|
||||
|
||||
const handleTextChange = (val: string) => {
|
||||
setInputValue(val);
|
||||
debouncedUpdateAttributes(val);
|
||||
};
|
||||
|
||||
const handleColorChange = (newColor: StatusColor) => {
|
||||
updateAttributes({ color: newColor });
|
||||
};
|
||||
|
||||
const isEditable = editor.isEditable;
|
||||
|
||||
return (
|
||||
<NodeViewWrapper style={{ display: "inline" }} data-drag-handle>
|
||||
<Popover
|
||||
opened={opened}
|
||||
onChange={(open) => {
|
||||
if (!open && !text) {
|
||||
deleteNode();
|
||||
return;
|
||||
}
|
||||
setOpened(open);
|
||||
}}
|
||||
width={220}
|
||||
position="bottom"
|
||||
withArrow
|
||||
shadow="md"
|
||||
trapFocus
|
||||
>
|
||||
<Popover.Target>
|
||||
<span
|
||||
className={clsx(
|
||||
"status-badge",
|
||||
classes.status,
|
||||
colorClassMap[color],
|
||||
)}
|
||||
onClick={() => isEditable && setOpened(true)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
{text || "SET STATUS"}
|
||||
</span>
|
||||
</Popover.Target>
|
||||
|
||||
<Popover.Dropdown>
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
value={inputValue}
|
||||
onChange={(e) =>
|
||||
handleTextChange(e.currentTarget.value.toUpperCase())
|
||||
}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
setOpened(false);
|
||||
}
|
||||
}}
|
||||
placeholder="Status text"
|
||||
size="sm"
|
||||
mb="xs"
|
||||
/>
|
||||
|
||||
<Group gap={6} justify="center">
|
||||
{STATUS_COLORS.map(({ name, bg }) => (
|
||||
<Box
|
||||
key={name}
|
||||
className={clsx(
|
||||
classes.swatch,
|
||||
color === name && classes.swatchActive,
|
||||
)}
|
||||
style={{ backgroundColor: bg }}
|
||||
onClick={() => handleColorChange(name)}
|
||||
>
|
||||
{color === name && <IconCheck size={14} />}
|
||||
</Box>
|
||||
))}
|
||||
</Group>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
</NodeViewWrapper>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
.status {
|
||||
display: inline-block;
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
border-radius: 3px;
|
||||
padding: 1px 6px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
line-height: 1.6;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.colorGray {
|
||||
background-color: light-dark(rgb(223 223 215), rgba(168, 162, 158, 0.4));
|
||||
color: light-dark(#3d3d3d, var(--mantine-color-gray-3));
|
||||
}
|
||||
|
||||
.colorBlue {
|
||||
background-color: light-dark(rgb(191 227 253), rgba(37, 99, 235, 0.4));
|
||||
color: light-dark(#1a4d99, var(--mantine-color-blue-3));
|
||||
}
|
||||
|
||||
.colorGreen {
|
||||
background-color: light-dark(rgb(187 240 173), rgba(0, 138, 0, 0.4));
|
||||
color: light-dark(#135c13, var(--mantine-color-green-3));
|
||||
}
|
||||
|
||||
.colorYellow {
|
||||
background-color: light-dark(rgb(249 238 148), rgba(234, 179, 8, 0.4));
|
||||
color: light-dark(#6b5300, var(--mantine-color-yellow-3));
|
||||
}
|
||||
|
||||
.colorRed {
|
||||
background-color: light-dark(rgb(255 200 195), rgba(224, 0, 0, 0.4));
|
||||
color: light-dark(#a10000, var(--mantine-color-red-3));
|
||||
}
|
||||
|
||||
.colorPurple {
|
||||
background-color: light-dark(rgb(225 207 245), rgba(147, 51, 234, 0.4));
|
||||
color: light-dark(#5b21a6, var(--mantine-color-violet-3));
|
||||
}
|
||||
|
||||
.swatch {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.swatch:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.swatchActive {
|
||||
border-color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-gray-4));
|
||||
}
|
||||
Reference in New Issue
Block a user