This commit is contained in:
fantasticit 2023-04-09 12:54:43 +08:00
parent 4eca312322
commit a6690f9edf
11 changed files with 303 additions and 6 deletions

View File

@ -0,0 +1,17 @@
import { Icon } from '@douyinfe/semi-ui';
export const IconLineHeight: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
return (
<Icon
style={style}
svg={
<svg width="16" height="16" viewBox="0 0 24 24" role="presentation">
<path
d="M11 4H21V6H11V4ZM6 7V11H4V7H1L5 3L9 7H6ZM6 17H9L5 21L1 17H4V13H6V17ZM11 18H21V20H11V18ZM9 11H21V13H9V11Z"
fill="currentColor"
></path>
</svg>
}
/>
);
};

View File

@ -30,6 +30,7 @@ export * from './IconInfo';
export * from './IconJSON'; export * from './IconJSON';
export * from './IconLayout'; export * from './IconLayout';
export * from './IconLeft'; export * from './IconLeft';
export * from './IconLineHeight';
export * from './IconLink'; export * from './IconLink';
export * from './IconList'; export * from './IconList';
export * from './IconMarkdown'; export * from './IconMarkdown';

View File

@ -30,6 +30,7 @@ import { Image } from 'tiptap/core/extensions/image';
import { Indent } from 'tiptap/core/extensions/indent'; import { Indent } from 'tiptap/core/extensions/indent';
import { Italic } from 'tiptap/core/extensions/italic'; import { Italic } from 'tiptap/core/extensions/italic';
import { Katex } from 'tiptap/core/extensions/katex'; import { Katex } from 'tiptap/core/extensions/katex';
import { LineHeight } from 'tiptap/core/extensions/line-height';
import { Link } from 'tiptap/core/extensions/link'; import { Link } from 'tiptap/core/extensions/link';
import { ListItem } from 'tiptap/core/extensions/listItem'; import { ListItem } from 'tiptap/core/extensions/listItem';
import { Loading } from 'tiptap/core/extensions/loading'; import { Loading } from 'tiptap/core/extensions/loading';
@ -80,6 +81,7 @@ export const AllExtensions = [
Excalidraw, Excalidraw,
Focus, Focus,
FontSize, FontSize,
LineHeight,
Gapcursor, Gapcursor,
HardBreak, HardBreak,
Heading, Heading,

View File

@ -1 +1,111 @@
export { Heading } from '@tiptap/extension-heading'; import { mergeAttributes, Node, textblockTypeInputRule } from '@tiptap/core';
export type Level = 1 | 2 | 3 | 4 | 5 | 6;
export interface HeadingOptions {
levels: Level[];
HTMLAttributes: Record<string, any>;
}
declare module '@tiptap/core' {
interface Commands<ReturnType> {
heading: {
/**
* Set a heading node
*/
setHeading: (attributes: { level: Level }) => ReturnType;
/**
* Toggle a heading node
*/
toggleHeading: (attributes: { level: Level }) => ReturnType;
};
}
}
export const Heading = Node.create<HeadingOptions>({
name: 'heading',
addOptions() {
return {
levels: [1, 2, 3, 4, 5, 6],
HTMLAttributes: {},
};
},
content: 'inline*',
group: 'block',
defining: true,
addAttributes() {
return {
level: {
default: 1,
rendered: false,
},
lineHeight: { default: null },
};
},
parseHTML() {
return this.options.levels.map((level: Level) => ({
tag: `h${level}`,
attrs: { level },
}));
},
renderHTML({ node, HTMLAttributes }) {
const hasLevel = this.options.levels.includes(node.attrs.level);
const level = hasLevel ? node.attrs.level : this.options.levels[0];
return [`h${level}`, mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
},
addCommands() {
return {
setHeading:
(attributes) =>
({ commands }) => {
if (!this.options.levels.includes(attributes.level)) {
return false;
}
return commands.setNode(this.name, attributes);
},
toggleHeading:
(attributes) =>
({ commands }) => {
if (!this.options.levels.includes(attributes.level)) {
return false;
}
return commands.toggleNode(this.name, 'paragraph', attributes);
},
};
},
addKeyboardShortcuts() {
return this.options.levels.reduce(
(items, level) => ({
...items,
...{
[`Mod-Alt-${level}`]: () => this.editor.commands.toggleHeading({ level }),
},
}),
{}
);
},
addInputRules() {
return this.options.levels.map((level) => {
return textblockTypeInputRule({
find: new RegExp(`^(#{1,${level}})\\s$`),
type: this.type,
getAttributes: {
level,
},
});
});
},
});

View File

@ -92,7 +92,7 @@ export const Indent = Extension.create<IndentOptions>({
indent: { indent: {
default: this.options.defaultIndentLevel, default: this.options.defaultIndentLevel,
renderHTML: (attributes) => ({ renderHTML: (attributes) => ({
style: `margin-left: ${attributes.indent}px!important;`, style: `margin-left: ${attributes.indent}px;`,
}), }),
parseHTML: (element) => parseInt(element.style.marginLeft) || this.options.defaultIndentLevel, parseHTML: (element) => parseInt(element.style.marginLeft) || this.options.defaultIndentLevel,
}, },

View File

@ -0,0 +1,58 @@
import { Extension } from '@tiptap/core';
declare module '@tiptap/core' {
interface Commands<ReturnType> {
lineHeight: {
setLineHeight: (val: number) => ReturnType;
unsetLineHeight: () => ReturnType;
};
}
}
export const LineHeight = Extension.create({
name: 'lineHeight',
addOptions() {
return {
types: ['heading', 'paragraph'],
};
},
addGlobalAttributes() {
return [
{
types: this.options.types,
attributes: {
fontSize: {
default: null,
parseHTML: (element) => element.style.lineHeight.replace(/['"]+/g, ''),
renderHTML: (attributes) => {
if (!attributes.lineHeight) {
return {};
}
return {
style: `line-height: ${attributes.lineHeight}`,
};
},
},
},
},
];
},
addCommands() {
return {
setLineHeight:
(lineHeight) =>
({ commands }) => {
return this.options.types.every((type) => commands.updateAttributes(type, { lineHeight }));
},
unsetLineHeight:
() =>
({ commands }) => {
return this.options.types.every((type) => commands.resetAttributes(type, 'lineHeight'));
},
};
},
});

View File

@ -1,6 +1,62 @@
import { mergeAttributes } from '@tiptap/core'; import { mergeAttributes, Node } from '@tiptap/core';
import TitapParagraph from '@tiptap/extension-paragraph';
export const Paragraph = TitapParagraph.extend({ export interface ParagraphOptions {
selectable: true, HTMLAttributes: Record<string, any>;
}
declare module '@tiptap/core' {
interface Commands<ReturnType> {
paragraph: {
/**
* Toggle a paragraph
*/
setParagraph: () => ReturnType;
};
}
}
export const Paragraph = Node.create<ParagraphOptions>({
name: 'paragraph',
priority: 1000,
addOptions() {
return {
HTMLAttributes: {},
};
},
addAttributes() {
return {
lineHeight: { default: null },
};
},
group: 'block',
content: 'inline*',
parseHTML() {
return [{ tag: 'p' }];
},
renderHTML({ HTMLAttributes, node }) {
return ['p', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
},
addCommands() {
return {
setParagraph:
() =>
({ commands }) => {
return commands.setNode(this.name);
},
};
},
addKeyboardShortcuts() {
return {
'Mod-Alt-0': () => this.editor.commands.setParagraph(),
};
},
}); });

View File

@ -24,6 +24,7 @@ export const Status = Node.create({
group: 'inline', group: 'inline',
inline: true, inline: true,
selectable: true, selectable: true,
draggable: true,
atom: true, atom: true,
addAttributes() { addAttributes() {

View File

@ -0,0 +1,48 @@
import { Button, Dropdown, Tooltip } from '@douyinfe/semi-ui';
import { IconLineHeight } from 'components/icons';
import React, { useCallback } from 'react';
import { Editor } from 'tiptap/core';
import { Title } from 'tiptap/core/extensions/title';
import { useActive } from 'tiptap/core/hooks/use-active';
import { useAttributes } from 'tiptap/core/hooks/use-attributes';
export const LINE_HEIGHT = [null, 1, 1.15, 1.5, 2, 2.5, 3];
export const LineHeight: React.FC<{ editor: Editor }> = ({ editor }) => {
const isTitleActive = useActive(editor, Title.name);
const currentValue = useAttributes(editor, 'textStyle', { lineHeight: null }, (attrs) => {
if (!attrs || !attrs.lineHeight) return null;
const matches = attrs.lineHeight.match(/\d+/);
if (!matches || !matches[0]) return 16;
return matches[0];
});
const toggle = useCallback(
(val) => {
if (val) {
editor.chain().focus().setLineHeight(val).run();
} else {
editor.chain().focus().unsetLineHeight().run();
}
},
[editor]
);
return (
<Dropdown
content={LINE_HEIGHT.map((val) => (
<Dropdown.Item key={val} onClick={() => toggle(val)}>
{val || '默认'}
</Dropdown.Item>
))}
>
<span>
<Tooltip content="行高">
<Button icon={<IconLineHeight />} theme={'borderless'} type="tertiary" disabled={isTitleActive} />
</Tooltip>
</span>
</Dropdown>
);
};

View File

@ -29,6 +29,7 @@ import { Image } from 'tiptap/core/menus/image';
import { Insert } from 'tiptap/core/menus/insert'; import { Insert } from 'tiptap/core/menus/insert';
import { Italic } from 'tiptap/core/menus/italic'; import { Italic } from 'tiptap/core/menus/italic';
import { Katex } from 'tiptap/core/menus/katex'; import { Katex } from 'tiptap/core/menus/katex';
import { LineHeight } from 'tiptap/core/menus/lineheight';
import { Link } from 'tiptap/core/menus/link'; import { Link } from 'tiptap/core/menus/link';
import { Mind } from 'tiptap/core/menus/mind'; import { Mind } from 'tiptap/core/menus/mind';
import { OrderedList } from 'tiptap/core/menus/ordered-list'; import { OrderedList } from 'tiptap/core/menus/ordered-list';
@ -91,6 +92,7 @@ const _MenuBar: React.FC<{ editor: Editor }> = ({ editor }) => {
<OrderedList editor={editor} /> <OrderedList editor={editor} />
<TaskList editor={editor} /> <TaskList editor={editor} />
<Ident editor={editor} /> <Ident editor={editor} />
<LineHeight editor={editor} />
<Divider /> <Divider />

View File

@ -38,6 +38,7 @@ import { Image } from 'tiptap/core/extensions/image';
import { Indent } from 'tiptap/core/extensions/indent'; import { Indent } from 'tiptap/core/extensions/indent';
import { Italic } from 'tiptap/core/extensions/italic'; import { Italic } from 'tiptap/core/extensions/italic';
import { Katex } from 'tiptap/core/extensions/katex'; import { Katex } from 'tiptap/core/extensions/katex';
import { LineHeight } from 'tiptap/core/extensions/line-height';
import { Link } from 'tiptap/core/extensions/link'; import { Link } from 'tiptap/core/extensions/link';
import { ListItem } from 'tiptap/core/extensions/listItem'; import { ListItem } from 'tiptap/core/extensions/listItem';
import { Loading } from 'tiptap/core/extensions/loading'; import { Loading } from 'tiptap/core/extensions/loading';
@ -127,6 +128,7 @@ export const CollaborationKit = [
Focus, Focus,
FontFamily, FontFamily,
FontSize, FontSize,
LineHeight,
Gapcursor, Gapcursor,
HardBreak, HardBreak,
Heading, Heading,