mirror of https://github.com/fantasticit/think.git
close #228
parent
4eca312322
commit
a6690f9edf
|
@ -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>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -30,6 +30,7 @@ export * from './IconInfo';
|
|||
export * from './IconJSON';
|
||||
export * from './IconLayout';
|
||||
export * from './IconLeft';
|
||||
export * from './IconLineHeight';
|
||||
export * from './IconLink';
|
||||
export * from './IconList';
|
||||
export * from './IconMarkdown';
|
||||
|
|
|
@ -30,6 +30,7 @@ import { Image } from 'tiptap/core/extensions/image';
|
|||
import { Indent } from 'tiptap/core/extensions/indent';
|
||||
import { Italic } from 'tiptap/core/extensions/italic';
|
||||
import { Katex } from 'tiptap/core/extensions/katex';
|
||||
import { LineHeight } from 'tiptap/core/extensions/line-height';
|
||||
import { Link } from 'tiptap/core/extensions/link';
|
||||
import { ListItem } from 'tiptap/core/extensions/listItem';
|
||||
import { Loading } from 'tiptap/core/extensions/loading';
|
||||
|
@ -80,6 +81,7 @@ export const AllExtensions = [
|
|||
Excalidraw,
|
||||
Focus,
|
||||
FontSize,
|
||||
LineHeight,
|
||||
Gapcursor,
|
||||
HardBreak,
|
||||
Heading,
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -92,7 +92,7 @@ export const Indent = Extension.create<IndentOptions>({
|
|||
indent: {
|
||||
default: this.options.defaultIndentLevel,
|
||||
renderHTML: (attributes) => ({
|
||||
style: `margin-left: ${attributes.indent}px!important;`,
|
||||
style: `margin-left: ${attributes.indent}px;`,
|
||||
}),
|
||||
parseHTML: (element) => parseInt(element.style.marginLeft) || this.options.defaultIndentLevel,
|
||||
},
|
||||
|
|
|
@ -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'));
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
|
@ -1,6 +1,62 @@
|
|||
import { mergeAttributes } from '@tiptap/core';
|
||||
import TitapParagraph from '@tiptap/extension-paragraph';
|
||||
import { mergeAttributes, Node } from '@tiptap/core';
|
||||
|
||||
export const Paragraph = TitapParagraph.extend({
|
||||
selectable: true,
|
||||
export interface ParagraphOptions {
|
||||
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(),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -24,6 +24,7 @@ export const Status = Node.create({
|
|||
group: 'inline',
|
||||
inline: true,
|
||||
selectable: true,
|
||||
draggable: true,
|
||||
atom: true,
|
||||
|
||||
addAttributes() {
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -29,6 +29,7 @@ import { Image } from 'tiptap/core/menus/image';
|
|||
import { Insert } from 'tiptap/core/menus/insert';
|
||||
import { Italic } from 'tiptap/core/menus/italic';
|
||||
import { Katex } from 'tiptap/core/menus/katex';
|
||||
import { LineHeight } from 'tiptap/core/menus/lineheight';
|
||||
import { Link } from 'tiptap/core/menus/link';
|
||||
import { Mind } from 'tiptap/core/menus/mind';
|
||||
import { OrderedList } from 'tiptap/core/menus/ordered-list';
|
||||
|
@ -91,6 +92,7 @@ const _MenuBar: React.FC<{ editor: Editor }> = ({ editor }) => {
|
|||
<OrderedList editor={editor} />
|
||||
<TaskList editor={editor} />
|
||||
<Ident editor={editor} />
|
||||
<LineHeight editor={editor} />
|
||||
|
||||
<Divider />
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ import { Image } from 'tiptap/core/extensions/image';
|
|||
import { Indent } from 'tiptap/core/extensions/indent';
|
||||
import { Italic } from 'tiptap/core/extensions/italic';
|
||||
import { Katex } from 'tiptap/core/extensions/katex';
|
||||
import { LineHeight } from 'tiptap/core/extensions/line-height';
|
||||
import { Link } from 'tiptap/core/extensions/link';
|
||||
import { ListItem } from 'tiptap/core/extensions/listItem';
|
||||
import { Loading } from 'tiptap/core/extensions/loading';
|
||||
|
@ -127,6 +128,7 @@ export const CollaborationKit = [
|
|||
Focus,
|
||||
FontFamily,
|
||||
FontSize,
|
||||
LineHeight,
|
||||
Gapcursor,
|
||||
HardBreak,
|
||||
Heading,
|
||||
|
|
Loading…
Reference in New Issue