feat: improve tiptap

This commit is contained in:
fantasticit 2022-03-21 00:39:55 +08:00
parent 1835f8504b
commit ea34e23422
26 changed files with 626 additions and 254 deletions

View File

@ -1,18 +0,0 @@
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
import { Space, Popover, Tag, Input } from '@douyinfe/semi-ui';
export const TableWrapper = ({ editor, node, updateAttributes }) => {
const isEditable = editor.isEditable;
const { color, text } = node.attrs;
const content = <Tag color={color}>{text || '点击设置状态'}</Tag>;
console.log(node.attrs);
return (
<NodeViewWrapper as="div">
<table>
<NodeViewContent></NodeViewContent>
</table>
</NodeViewWrapper>
);
};

View File

@ -1,5 +1,14 @@
import { Table as BuiltInTable } from '@tiptap/extension-table';
import { TableView } from '../views/tableView';
export const Table = BuiltInTable.configure({
export const Table = BuiltInTable.extend({
// @ts-ignore
addOptions() {
return {
...this.parent?.(),
View: TableView,
};
},
}).configure({
resizable: true,
});

View File

@ -1,120 +0,0 @@
import { TableCell as BuiltInTable } from '@tiptap/extension-table-cell';
import { Plugin, PluginKey } from 'prosemirror-state';
import { Decoration, DecorationSet } from 'prosemirror-view';
import {
getCellsInColumn,
isRowSelected,
isTableSelected,
selectRow,
selectTable,
} from '../services/table';
export const TableCell = BuiltInTable.extend({
addProseMirrorPlugins() {
return [
new Plugin({
key: new PluginKey(`${this.name}FloatMenu`),
// view: () =>
// new FloatMenuView({
// editor: this.editor,
// // has one selected should show
// shouldShow: ({ editor }) => {
// if (!editor.isEditable) {
// return false;
// }
// const cells = getCellsInColumn(0)(editor.state.selection);
// return !!cells?.some((cell, index) =>
// isRowSelected(index)(editor.state.selection)
// );
// },
// init: (dom, editor) => {
// const insertTop = buttonView({
// id: "insert-top",
// name: this.options.dictionary.insertTop,
// icon: DoubleUp({}),
// });
// insertTop.button.addEventListener("click", () => {
// editor.chain().addRowBefore().run();
// });
// const insertBottom = buttonView({
// id: "insert-bottom",
// name: this.options.dictionary.insertBottom,
// icon: DoubleDown({}),
// });
// insertBottom.button.addEventListener("click", () => {
// editor.chain().addRowAfter().run();
// });
// const remove = buttonView({
// name: this.options.dictionary.delete,
// icon: Delete({}),
// });
// remove.button.addEventListener("click", () => {
// if (isTableSelected(editor.state.selection)) {
// editor.chain().deleteTable().run();
// } else {
// editor.chain().deleteRow().run();
// }
// });
// dom.append(insertTop.button);
// dom.append(insertBottom.button);
// dom.append(remove.button);
// },
// }),
props: {
decorations: (state) => {
const { doc, selection } = state;
const decorations: Decoration[] = [];
const cells = getCellsInColumn(0)(selection);
if (cells) {
cells.forEach(({ pos }, index) => {
if (index === 0) {
decorations.push(
Decoration.widget(pos + 1, () => {
const grip = document.createElement('a');
grip.classList.add('grip-table');
if (isTableSelected(selection)) {
grip.classList.add('selected');
}
grip.addEventListener('mousedown', (event) => {
event.preventDefault();
event.stopImmediatePropagation();
this.editor.view.dispatch(selectTable(this.editor.state.tr));
});
return grip;
})
);
}
decorations.push(
Decoration.widget(pos + 1, () => {
const rowSelected = isRowSelected(index)(selection);
const grip = document.createElement('a');
grip.classList.add('grip-row');
if (rowSelected) {
grip.classList.add('selected');
}
if (index === 0) {
grip.classList.add('first');
}
if (index === cells.length - 1) {
grip.classList.add('last');
}
grip.addEventListener('mousedown', (event) => {
event.preventDefault();
event.stopImmediatePropagation();
this.editor.view.dispatch(selectRow(index)(this.editor.state.tr));
});
return grip;
})
);
});
}
return DecorationSet.create(doc, decorations);
},
},
}),
];
},
});

View File

@ -0,0 +1,162 @@
import ReactDOM from 'react-dom';
import { Button } from '@douyinfe/semi-ui';
import { IconDelete, IconPlus } from '@douyinfe/semi-icons';
import { TableCell as BuiltInTableCell } from '@tiptap/extension-table-cell';
import { Tooltip } from 'components/tooltip';
import { Plugin, PluginKey } from 'prosemirror-state';
import { Decoration, DecorationSet } from 'prosemirror-view';
import {
getCellsInRow,
getCellsInColumn,
isRowSelected,
isTableSelected,
selectRow,
selectTable,
} from '../services/table';
import { elementInViewport } from '../services/dom';
import { FloatMenuView } from '../views/floatMenuView';
export const TableCell = BuiltInTableCell.extend({
addProseMirrorPlugins() {
const extensionThis = this;
let selectedRowIndex = -1;
return [
new Plugin({
key: new PluginKey(`${this.name}FloatMenu`),
view: () =>
new FloatMenuView({
editor: this.editor,
shouldShow: ({ editor }, floatMenuView) => {
if (!editor.isEditable) {
return false;
}
const cells = getCellsInColumn(0)(editor.state.selection);
if (selectedRowIndex > -1) {
const rowCells = getCellsInRow(selectedRowIndex)(editor.state.selection);
if (rowCells && rowCells[0]) {
const node = editor.view.nodeDOM(rowCells[0].pos) as HTMLElement;
if (node) {
const el = node.querySelector('a.grip-row') as HTMLElement;
if (el) {
console.log({ el });
floatMenuView.parentNode = el;
// const intersectionObserver = new IntersectionObserver(function (entries) {
// console.log('ob');
// if (entries[0].intersectionRatio <= 0) {
// floatMenuView.hide();
// }
// });
// intersectionObserver.observe(el);
}
}
}
}
return !!cells?.some((cell, index) => isRowSelected(index)(editor.state.selection));
},
init: (dom, editor) => {
dom.classList.add('table-controller-wrapper');
dom.classList.add('row');
ReactDOM.render(
<>
<Tooltip content="向前插入一行" position="left">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconPlus />}
onClick={() => {
editor.chain().addRowBefore().run();
}}
/>
</Tooltip>
<Tooltip content="删除当前行" position="left">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconDelete />}
onClick={() => {
editor.chain().deleteRow().run();
}}
/>
</Tooltip>
<Tooltip content="向后插入一行" position="left" hideOnClick>
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconPlus />}
onClick={() => {
editor.chain().addRowAfter().run();
}}
/>
</Tooltip>
</>,
dom
);
},
}),
props: {
decorations: (state) => {
if (!extensionThis.editor.isEditable) {
return;
}
const { doc, selection } = state;
const decorations: Decoration[] = [];
const cells = getCellsInColumn(0)(selection);
if (cells) {
cells.forEach(({ pos }, index) => {
if (index === 0) {
decorations.push(
Decoration.widget(pos + 1, () => {
const grip = document.createElement('a');
grip.classList.add('grip-table');
if (isTableSelected(selection)) {
grip.classList.add('selected');
}
grip.addEventListener('mousedown', (event) => {
event.preventDefault();
event.stopImmediatePropagation();
selectedRowIndex = -1;
this.editor.view.dispatch(selectTable(this.editor.state.tr));
});
return grip;
})
);
}
decorations.push(
Decoration.widget(pos + 1, () => {
const rowSelected = isRowSelected(index)(selection);
const grip = document.createElement('a');
grip.classList.add('grip-row');
if (rowSelected) {
grip.classList.add('selected');
}
if (index === 0) {
grip.classList.add('first');
}
if (index === cells.length - 1) {
grip.classList.add('last');
}
grip.addEventListener('mousedown', (event) => {
event.preventDefault();
event.stopImmediatePropagation();
selectedRowIndex = index;
this.editor.view.dispatch(selectRow(index)(this.editor.state.tr));
});
return grip;
})
);
});
}
return DecorationSet.create(doc, decorations);
},
},
}),
];
},
});

View File

@ -1,94 +0,0 @@
import { TableHeader as BuiltInTableHeader } from '@tiptap/extension-table-header';
import { Plugin, PluginKey } from 'prosemirror-state';
import { Decoration, DecorationSet } from 'prosemirror-view';
import { getCellsInRow, isColumnSelected, isTableSelected, selectColumn } from '../services/table';
export const TableHeader = BuiltInTableHeader.extend({
addProseMirrorPlugins() {
return [
new Plugin({
key: new PluginKey(`${this.name}FloatMenu`),
// view: () =>
// new FloatMenuView({
// editor: this.editor,
// // has one selected should show
// shouldShow: ({ editor }) => {
// if (!editor.isEditable) {
// return false;
// }
// const selection = editor.state.selection;
// if (isTableSelected(selection)) {
// return false;
// }
// const cells = getCellsInRow(0)(selection);
// return !!cells?.some((cell, index) =>
// isColumnSelected(index)(selection)
// );
// },
// init: (dom, editor) => {
// const insertLeft = buttonView({
// name: this.options.dictionary.insertLeft,
// icon: DoubleLeft({}),
// });
// insertLeft.button.addEventListener("click", () => {
// editor.chain().addColumnBefore().run();
// });
// const insertRight = buttonView({
// name: this.options.dictionary.insertRight,
// icon: DoubleRight({}),
// });
// insertRight.button.addEventListener("click", () => {
// editor.chain().addColumnAfter().run();
// });
// const remove = buttonView({
// name: this.options.dictionary.delete,
// icon: Delete({}),
// });
// remove.button.addEventListener("click", () => {
// editor.chain().deleteColumn().run();
// });
// dom.append(insertLeft.button);
// dom.append(insertRight.button);
// dom.append(remove.button);
// },
// }),
props: {
decorations: (state) => {
const { doc, selection } = state;
const decorations: Decoration[] = [];
const cells = getCellsInRow(0)(selection);
if (cells) {
cells.forEach(({ pos }, index) => {
decorations.push(
Decoration.widget(pos + 1, () => {
const colSelected = isColumnSelected(index)(selection);
const grip = document.createElement('a');
grip.classList.add('grip-column');
if (colSelected) {
grip.classList.add('selected');
}
if (index === 0) {
grip.classList.add('first');
} else if (index === cells.length - 1) {
grip.classList.add('last');
}
grip.addEventListener('mousedown', (event) => {
event.preventDefault();
event.stopImmediatePropagation();
this.editor.view.dispatch(selectColumn(index)(this.editor.state.tr));
});
return grip;
})
);
});
}
return DecorationSet.create(doc, decorations);
},
},
}),
];
},
});

View File

@ -0,0 +1,116 @@
import ReactDOM from 'react-dom';
import { Button, Space } from '@douyinfe/semi-ui';
import { IconDelete, IconPlus } from '@douyinfe/semi-icons';
import { TableHeader as BuiltInTableHeader } from '@tiptap/extension-table-header';
import { Tooltip } from 'components/tooltip';
import { Plugin, PluginKey } from 'prosemirror-state';
import { Decoration, DecorationSet } from 'prosemirror-view';
import { getCellsInRow, isColumnSelected, isTableSelected, selectColumn } from '../services/table';
import { FloatMenuView } from '../views/floatMenuView';
export const TableHeader = BuiltInTableHeader.extend({
addProseMirrorPlugins() {
const extensionThis = this;
return [
new Plugin({
key: new PluginKey(`${this.name}FloatMenu`),
view: () =>
new FloatMenuView({
editor: this.editor,
shouldShow: ({ editor }) => {
if (!editor.isEditable) {
return false;
}
const selection = editor.state.selection;
if (isTableSelected(selection)) {
return false;
}
const cells = getCellsInRow(0)(selection);
return !!cells?.some((cell, index) => isColumnSelected(index)(selection));
},
init: (dom, editor) => {
dom.classList.add('table-controller-wrapper');
ReactDOM.render(
<Space>
<Tooltip content="向前插入一列">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconPlus />}
onClick={() => {
editor.chain().addColumnBefore().run();
}}
/>
</Tooltip>
<Tooltip content="向后插入一列">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconDelete />}
onClick={() => {
editor.chain().deleteColumn().run();
}}
/>
</Tooltip>
<Tooltip content="删除当前列" hideOnClick>
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconPlus />}
onClick={() => {
editor.chain().addColumnAfter().run();
}}
/>
</Tooltip>
</Space>,
dom
);
},
}),
props: {
decorations: (state) => {
if (!extensionThis.editor.isEditable) {
return;
}
const { doc, selection } = state;
const decorations: Decoration[] = [];
const cells = getCellsInRow(0)(selection);
if (cells) {
cells.forEach(({ pos }, index) => {
decorations.push(
Decoration.widget(pos + 1, () => {
const colSelected = isColumnSelected(index)(selection);
const grip = document.createElement('a');
grip.classList.add('grip-column');
if (colSelected) {
grip.classList.add('selected');
}
if (index === 0) {
grip.classList.add('first');
} else if (index === cells.length - 1) {
grip.classList.add('last');
}
grip.addEventListener('mousedown', (event) => {
event.preventDefault();
event.stopImmediatePropagation();
this.editor.view.dispatch(selectColumn(index)(this.editor.state.tr));
});
return grip;
})
);
});
}
return DecorationSet.create(doc, decorations);
},
},
}),
];
},
});

View File

@ -3,14 +3,14 @@ import { Space, Button, Tooltip } from '@douyinfe/semi-ui';
import { IconUndo, IconRedo } from '@douyinfe/semi-icons';
import { IconClear } from 'components/icons';
import { Divider } from './components/divider';
import { MediaInsertMenu } from './menus/media-insert';
import { MediaInsertMenu } from './menus/mediaInsert';
import { Paragraph } from './menus/components/paragraph';
import { FontSize } from './menus/components/font-size';
import { BaseMenu } from './menus/base-menu';
import { FontSize } from './menus/components/fontSize';
import { BaseMenu } from './menus/baseMenu';
import { AlignMenu } from './menus/align';
import { ListMenu } from './menus/list';
import { BaseInsertMenu } from './menus/base-insert';
import { BaseBubbleMenu } from './menus/base-bubble-menu';
import { BaseInsertMenu } from './menus/baseInsert';
import { BaseBubbleMenu } from './menus/baseBubbleMenu';
import { ImageBubbleMenu } from './menus/image';
import { BannerBubbleMenu } from './menus/banner';
import { LinkBubbleMenu } from './menus/link';

View File

@ -7,7 +7,7 @@ import {
IconInfoCircle,
} from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip';
import { BubbleMenu } from './components/bubble-menu';
import { BubbleMenu } from './components/bubbleMenu';
import { Divider } from '../components/divider';
import { Banner } from '../extensions/banner';
import { deleteNode } from '../services/deleteNode';

View File

@ -13,7 +13,7 @@ import { Table } from '../extensions/table';
import { Katex } from '../extensions/katex';
import { DocumentReference } from '../extensions/documentReference';
import { DocumentChildren } from '../extensions/documentChildren';
import { BaseMenu } from './base-menu';
import { BaseMenu } from './baseMenu';
const OTHER_BUBBLE_MENU_TYPES = [
Title.name,

View File

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { BubbleMenuPlugin, BubbleMenuPluginProps } from './bubble-menu-plugin';
import { BubbleMenuPlugin, BubbleMenuPluginProps } from './bubbleMenuPlugin';
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;

View File

@ -9,7 +9,7 @@ import {
} from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip';
import { Upload } from 'components/upload';
import { BubbleMenu } from './components/bubble-menu';
import { BubbleMenu } from './components/bubbleMenu';
import { Divider } from '../components/divider';
import { Image } from '../extensions/image';
import { getImageOriginSize } from '../services/image';

View File

@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
import { Space, Button, Input } from '@douyinfe/semi-ui';
import { IconExternalOpen, IconUnlink, IconTickCircle } from '@douyinfe/semi-icons';
import { Tooltip } from 'components/tooltip';
import { BubbleMenu } from './components/bubble-menu';
import { BubbleMenu } from './components/bubbleMenu';
import { Link } from '../extensions/link';
export const LinkBubbleMenu = ({ editor }) => {

View File

@ -11,13 +11,13 @@ import {
IconDeleteTable,
} from 'components/icons';
import { Tooltip } from 'components/tooltip';
import { BubbleMenu } from './components/bubble-menu';
import { BubbleMenu } from './components/bubbleMenu';
import { Table } from '../extensions/table';
export const TableBubbleMenu = ({ editor }) => {
return (
<BubbleMenu
className={'bubble-menu'}
className={'bubble-menu table-bubble-menu'}
editor={editor}
pluginKey="table-bubble-menu"
shouldShow={() => editor.isActive(Table.name)}

View File

@ -9,3 +9,23 @@ export const getParents = (element) => {
return parents;
};
export function elementInViewport(el) {
let top = el.offsetTop;
let left = el.offsetLeft;
const width = el.offsetWidth;
const height = el.offsetHeight;
while (el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top < window.pageYOffset + window.innerHeight &&
left < window.pageXOffset + window.innerWidth &&
top + height > window.pageYOffset &&
left + width > window.pageXOffset
);
}

View File

@ -10,7 +10,11 @@ import splitMixedLists from './markedownSplitMixedList';
import markdownUnderline from './markdownUnderline';
import markdownBanner from './markdownBanner';
export const markdown = markdownit('commonmark', { html: false, breaks: false })
export const markdown = markdownit({
html: true,
linkify: true,
typographer: true,
})
.enable('strikethrough')
.use(sub)
.use(sup)

View File

@ -0,0 +1,150 @@
import { Editor, isNodeSelection, posToDOMRect, Range } from '@tiptap/core';
import tippy, { Instance, Props } from 'tippy.js';
import { EditorView } from 'prosemirror-view';
import { EditorState } from 'prosemirror-state';
export type FloatMenuViewOptions = {
editor: Editor;
getReferenceClientRect?: (props: {
editor: Editor;
range: Range;
oldState?: EditorState;
}) => DOMRect;
shouldShow: (
props: { editor: Editor; range: Range; oldState?: EditorState },
instance: FloatMenuView
) => boolean;
init: (dom: HTMLElement, editor: Editor) => void;
update?: (
dom: HTMLElement,
props: {
editor: Editor;
oldState?: EditorState;
range: Range;
show: () => void;
hide: () => void;
}
) => void;
tippyOptions?: Partial<Props>;
};
export class FloatMenuView {
public editor: Editor;
public parentNode: null | HTMLElement;
private dom: HTMLElement;
private popup: Instance;
private _update: FloatMenuViewOptions['update'];
private shouldShow: FloatMenuViewOptions['shouldShow'];
private getReferenceClientRect: NonNullable<FloatMenuViewOptions['getReferenceClientRect']> = ({
editor,
range,
}) => {
const { view, state } = editor;
if (isNodeSelection(state.selection)) {
const node = view.nodeDOM(range.from) as HTMLElement;
if (node) {
return node.getBoundingClientRect();
}
}
return posToDOMRect(view, range.from, range.to);
};
constructor(props: FloatMenuViewOptions) {
this.editor = props.editor;
this.shouldShow = props.shouldShow;
if (props.getReferenceClientRect) {
this.getReferenceClientRect = props.getReferenceClientRect;
}
this._update = props.update;
this.dom = document.createElement('div');
// init
props.init(this.dom, this.editor);
// popup
this.popup = tippy(document.body, {
appendTo: () => document.body,
getReferenceClientRect: null,
content: this.dom,
interactive: true,
trigger: 'manual',
placement: 'top',
hideOnClick: 'toggle',
...(props.tippyOptions ?? {}),
});
}
public update(view: EditorView, oldState?: EditorState) {
const { state, composing } = view;
const { doc, selection } = state;
const isSame = oldState && oldState.doc.eq(doc) && oldState.selection.eq(selection);
if (composing || isSame) {
return;
}
const { ranges } = selection;
const from = Math.min(...ranges.map((range) => range.$from.pos));
const to = Math.max(...ranges.map((range) => range.$to.pos));
const shouldShow = this.shouldShow?.(
{
editor: this.editor,
oldState,
range: {
from,
to,
},
},
this
);
if (!shouldShow) {
this.hide();
return;
}
this._update?.(this.dom, {
editor: this.editor,
oldState,
range: {
from,
to,
},
show: this.show.bind(this),
hide: this.hide.bind(this),
});
this.popup.setProps({
getReferenceClientRect: () => {
if (this.parentNode) {
return this.parentNode.getBoundingClientRect();
}
return this.getReferenceClientRect({
editor: this.editor,
oldState,
range: {
from,
to,
},
});
},
});
this.show();
}
show() {
this.popup.show();
}
hide() {
this.popup.hide();
}
public destroy() {
this.popup.destroy();
}
}

View File

@ -0,0 +1,103 @@
// @ts-nocheck
import { NodeView } from 'prosemirror-view';
import { Node as ProseMirrorNode } from 'prosemirror-model';
export function updateColumns(
node: ProseMirrorNode,
colgroup: Element,
table: Element,
cellMinWidth: number,
overrideCol?: number,
overrideValue?: any
) {
let totalWidth = 0;
let fixedWidth = true;
let nextDOM = colgroup.firstChild;
const row = node.firstChild;
for (let i = 0, col = 0; i < row.childCount; i += 1) {
const { colspan, colwidth } = row.child(i).attrs;
for (let j = 0; j < colspan; j += 1, col += 1) {
const hasWidth = overrideCol === col ? overrideValue : colwidth && colwidth[j];
const cssWidth = hasWidth ? `${hasWidth}px` : '';
totalWidth += hasWidth || cellMinWidth;
if (!hasWidth) {
fixedWidth = false;
}
if (!nextDOM) {
colgroup.appendChild(document.createElement('col')).style.width = cssWidth;
} else {
if (nextDOM.style.width !== cssWidth) {
nextDOM.style.width = cssWidth;
}
nextDOM = nextDOM.nextSibling;
}
}
}
while (nextDOM) {
const after = nextDOM.nextSibling;
nextDOM.parentNode.removeChild(nextDOM);
nextDOM = after;
}
if (fixedWidth) {
table.style.width = `${totalWidth}px`;
table.style.minWidth = '';
} else {
table.style.width = '';
table.style.minWidth = `${totalWidth}px`;
}
}
export class TableView implements NodeView {
node: ProseMirrorNode;
cellMinWidth: number;
dom: Element;
table: Element;
colgroup: Element;
contentDOM: Element;
constructor(node: ProseMirrorNode, cellMinWidth: number) {
this.node = node;
this.cellMinWidth = cellMinWidth;
this.dom = document.createElement('div');
this.dom.className = 'tableWrapper';
this.innerDom = document.createElement('div');
this.innerDom.className = 'tableInnerWrapper';
this.dom.appendChild(this.innerDom);
this.table = this.innerDom.appendChild(document.createElement('table'));
this.colgroup = this.table.appendChild(document.createElement('colgroup'));
updateColumns(node, this.colgroup, this.table, cellMinWidth);
this.contentDOM = this.table.appendChild(document.createElement('tbody'));
}
update(node: ProseMirrorNode) {
if (node.type !== this.node.type) {
return false;
}
this.node = node;
updateColumns(node, this.colgroup, this.table, this.cellMinWidth);
return true;
}
ignoreMutation(mutation: MutationRecord | { type: 'selection'; target: Element }) {
return (
mutation.type === 'attributes' &&
(mutation.target === this.table || this.colgroup.contains(mutation.target))
);
}
}

View File

@ -1,5 +1,6 @@
import React from 'react';
import { Tooltip as SemiTooltip } from '@douyinfe/semi-ui';
import { Position } from '@douyinfe/semi-ui/tooltip';
import { useToggle } from 'hooks/useToggle';
let id = 0;
@ -7,13 +8,25 @@ let id = 0;
interface IProps {
content: React.ReactNode;
hideOnClick?: boolean;
position?: Position;
}
export const Tooltip: React.FC<IProps> = ({ content, hideOnClick = false, children }) => {
export const Tooltip: React.FC<IProps> = ({
content,
hideOnClick = false,
position = 'top',
children,
}) => {
const [visible, toggleVisible] = useToggle(false);
return (
<SemiTooltip visible={visible} content={content} zIndex={10000} trigger={'custom'}>
<SemiTooltip
visible={visible}
content={content}
zIndex={10000}
trigger={'custom'}
position={position}
>
<span
onMouseEnter={() => {
toggleVisible(true);

View File

@ -6,6 +6,26 @@
box-shadow: var(--box-shadow);
background-color: var(--semi-color-nav-bg);
overflow-x: auto;
&.table-bubble-menu {
transform: translateY(-1em);
}
}
.table-controller-wrapper {
display: flex;
padding: 4px;
border-radius: 3px;
box-shadow: var(--box-shadow);
background-color: var(--semi-color-nav-bg);
transform: translateY(-1em);
&.row {
column-gap: 8px;
flex-direction: column;
transform: translate(-100%, 75%);
margin-left: -1.2em;
}
}
.resizeable-image-container {
@ -49,7 +69,7 @@
background-color: var(--color);
border: 1px solid rgba(128, 128, 128, 0.3);
border-radius: 2px;
content: " ";
content: ' ';
display: inline-block;
height: 1em;
margin-bottom: 0.15em;

View File

@ -110,7 +110,6 @@
border-bottom: 1px solid var(--semi-color-border);
}
h1,
h2,
h3,
h4,
@ -131,6 +130,16 @@
h1:not(.title):before {
content: 'H1';
&::before {
display: inline-block;
font-family: var(--tiptap-font-family-mono);
color: var(--tiptap-color-text-secondly);
font-size: 13px;
line-height: 0;
margin-left: -24px;
width: 24px;
}
}
h2::before {
@ -284,16 +293,13 @@
.tableWrapper {
width: 100%;
overflow: auto;
padding: 1em 0 0 1em;
margin: 1em 0;
}
table {
border-collapse: collapse;
table-layout: fixed;
width: 100%;
margin: 14px 0;
overflow: visible;
td,
th {
@ -305,6 +311,7 @@
padding: 3px 5px;
position: relative;
vertical-align: top;
overflow: visible;
> * {
margin-bottom: 0;