mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
enhance columns
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus";
|
||||
import { findParentNode, posToDOMRect, useEditorState } from "@tiptap/react";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { Node as PMNode } from "prosemirror-model";
|
||||
import React, { useCallback, useRef, useState } from "react";
|
||||
import { DOMSerializer, Node as PMNode } from "prosemirror-model";
|
||||
import {
|
||||
EditorMenuProps,
|
||||
ShouldShowProps,
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
IconLayoutSidebar,
|
||||
IconLayoutSidebarRight,
|
||||
IconLayoutAlignCenter,
|
||||
IconCopy,
|
||||
IconTrash,
|
||||
} from "@tabler/icons-react";
|
||||
import { isTextSelected } from "@docmost/editor-ext";
|
||||
import type { WidthMode, ColumnsLayout } from "@docmost/editor-ext";
|
||||
@@ -67,6 +69,8 @@ function getPresetsForCount(count: number): LayoutPreset[] {
|
||||
export function ColumnsMenu({ editor }: EditorMenuProps) {
|
||||
const { t } = useTranslation();
|
||||
const [isCountOpen, setIsCountOpen] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
const copyTimerRef = useRef<ReturnType<typeof setTimeout>>();
|
||||
|
||||
const nodesWithMenus = [
|
||||
"callout",
|
||||
@@ -187,6 +191,68 @@ export function ColumnsMenu({ editor }: EditorMenuProps) {
|
||||
[editor],
|
||||
);
|
||||
|
||||
const handleCopy = useCallback(() => {
|
||||
const { state } = editor;
|
||||
const parent = findParentNode(
|
||||
(node: PMNode) => node.type.name === "columns",
|
||||
)(state.selection);
|
||||
if (!parent) return;
|
||||
|
||||
const serializer = DOMSerializer.fromSchema(state.schema);
|
||||
const dom = serializer.serializeNode(parent.node);
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.appendChild(dom);
|
||||
|
||||
const onSuccess = () => {
|
||||
clearTimeout(copyTimerRef.current);
|
||||
setCopied(true);
|
||||
copyTimerRef.current = setTimeout(() => setCopied(false), 1500);
|
||||
};
|
||||
|
||||
if (navigator.clipboard?.write) {
|
||||
navigator.clipboard
|
||||
.write([
|
||||
new ClipboardItem({
|
||||
"text/html": new Blob([wrapper.innerHTML], { type: "text/html" }),
|
||||
"text/plain": new Blob([parent.node.textContent], {
|
||||
type: "text/plain",
|
||||
}),
|
||||
}),
|
||||
])
|
||||
.then(onSuccess)
|
||||
.catch(execCommandFallback);
|
||||
} else {
|
||||
execCommandFallback();
|
||||
}
|
||||
|
||||
function execCommandFallback() {
|
||||
wrapper.style.position = "fixed";
|
||||
wrapper.style.left = "-9999px";
|
||||
document.body.appendChild(wrapper);
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(wrapper);
|
||||
const sel = window.getSelection();
|
||||
sel?.removeAllRanges();
|
||||
sel?.addRange(range);
|
||||
document.execCommand("copy");
|
||||
sel?.removeAllRanges();
|
||||
document.body.removeChild(wrapper);
|
||||
editor.view.focus();
|
||||
onSuccess();
|
||||
}
|
||||
}, [editor]);
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
const { state } = editor;
|
||||
const parent = findParentNode(
|
||||
(node: PMNode) => node.type.name === "columns",
|
||||
)(state.selection);
|
||||
if (!parent) return;
|
||||
const { tr } = state;
|
||||
tr.delete(parent.pos, parent.pos + parent.node.nodeSize);
|
||||
editor.view.dispatch(tr);
|
||||
}, [editor]);
|
||||
|
||||
const columnCount = editorState?.columnCount || 2;
|
||||
const currentLayout = editorState?.layout || "two_equal";
|
||||
const presets = getPresetsForCount(columnCount);
|
||||
@@ -259,6 +325,34 @@ export function ColumnsMenu({ editor }: EditorMenuProps) {
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
))}
|
||||
|
||||
<div className={classes.divider} />
|
||||
|
||||
<Tooltip position="top" label={copied ? t("Copied") : t("Copy")} withinPortal={false}>
|
||||
<ActionIcon
|
||||
onClick={handleCopy}
|
||||
size="lg"
|
||||
aria-label={t("Copy")}
|
||||
variant="subtle"
|
||||
>
|
||||
{copied ? (
|
||||
<IconCheck size={18} color="var(--mantine-color-green-6)" />
|
||||
) : (
|
||||
<IconCopy size={18} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip position="top" label={t("Delete")} withinPortal={false}>
|
||||
<ActionIcon
|
||||
onClick={handleDelete}
|
||||
size="lg"
|
||||
aria-label={t("Delete")}
|
||||
variant="subtle"
|
||||
>
|
||||
<IconTrash size={18} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</BaseBubbleMenu>
|
||||
);
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
div[data-type="columns"] {
|
||||
display: flex;
|
||||
margin: 0.75rem 0;
|
||||
padding: 0.5em;
|
||||
padding: 0.5em 0;
|
||||
}
|
||||
|
||||
div[data-type="columns"] > div[data-type="column"] {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
div[data-type="columns"] > div[data-type="column"]:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
div[data-type="columns"] > div[data-type="column"] + div[data-type="column"] {
|
||||
@@ -16,6 +21,9 @@ div[data-type="columns"] > div[data-type="column"] + div[data-type="column"] {
|
||||
}
|
||||
|
||||
div[data-type="columns"]:hover
|
||||
> div[data-type="column"]
|
||||
+ div[data-type="column"],
|
||||
div[data-type="columns"].has-focus
|
||||
> div[data-type="column"]
|
||||
+ div[data-type="column"] {
|
||||
border-left: 1px solid
|
||||
@@ -66,7 +74,7 @@ div[data-type="columns"][data-layout="three_with_sidebars"]
|
||||
}
|
||||
|
||||
/* Stack columns vertically on small viewports */
|
||||
@media (max-width: 820px) {
|
||||
@media (max-width: 680px) {
|
||||
div[data-type="columns"] {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user