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 './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';
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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: {
|
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,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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 { 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(),
|
||||||
|
};
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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 { 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 />
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue