From c840aa140bf054fa3bbcac34410d564633016d13 Mon Sep 17 00:00:00 2001 From: fantasticit Date: Sun, 9 Apr 2023 12:13:13 +0800 Subject: [PATCH 1/7] close #232 --- .../client/src/tiptap/editor/tocs/index.tsx | 8 ++-- .../client/src/tiptap/editor/tocs/util.ts | 44 ++++++++++++++----- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/packages/client/src/tiptap/editor/tocs/index.tsx b/packages/client/src/tiptap/editor/tocs/index.tsx index 265bee73..aa13776d 100644 --- a/packages/client/src/tiptap/editor/tocs/index.tsx +++ b/packages/client/src/tiptap/editor/tocs/index.tsx @@ -9,7 +9,7 @@ import { TableOfContents } from 'tiptap/core/extensions/table-of-contents'; import { findNode } from 'tiptap/prose-utils'; import styles from './index.module.scss'; -import { flattenHeadingsToTree } from './util'; +import { flattenHeadingsToTree, parseHeadingsToTocs } from './util'; interface IHeading { level: number; @@ -124,7 +124,7 @@ export const Tocs: React.FC<{ editor: Editor; getContainer: () => HTMLElement }> editor.view.dispatch(transaction); setHeadings(headings); - setNestedHeadings(flattenHeadingsToTree(headings)); + setNestedHeadings(parseHeadingsToTocs(headings)); }, [editor]); useEffect(() => { @@ -161,7 +161,7 @@ export const Tocs: React.FC<{ editor: Editor; getContainer: () => HTMLElement }> ? headings.map((toc) => { return ( @@ -171,7 +171,7 @@ export const Tocs: React.FC<{ editor: Editor; getContainer: () => HTMLElement }> /> ); }) - : nestedHeadings.map((toc) => )} + : nestedHeadings.map((toc) => )} ); diff --git a/packages/client/src/tiptap/editor/tocs/util.ts b/packages/client/src/tiptap/editor/tocs/util.ts index d651dc22..485fb22b 100644 --- a/packages/client/src/tiptap/editor/tocs/util.ts +++ b/packages/client/src/tiptap/editor/tocs/util.ts @@ -1,18 +1,38 @@ -export const flattenHeadingsToTree = (tocs) => { - const result = []; - const levels = [result]; +export const parseHeadingsToTocs = (headings) => { + const list = JSON.parse(JSON.stringify(headings)); + const ret = []; - tocs.forEach((o) => { - let offset = -1; - let parent = levels[o.level + offset]; + list.forEach((heading, index) => { + const prev = list[index - 1]; - while (!parent) { - offset -= 1; - parent = levels[o.level + offset]; + if (!prev) { + ret.push(heading); + } else { + if (prev.level < heading.level) { + heading.parent = prev; + prev.children = prev.children || []; + prev.children.push(heading); + } else { + let parent = prev.parent; + + let shouldContinue = true; + + while (shouldContinue) { + if (!parent) { + shouldContinue = false; + ret.push(heading); + } else if (parent.level < heading.level) { + heading.parent = parent; + parent.children = parent.children || []; + parent.children.push(heading); + shouldContinue = false; + } else { + parent = parent.parent; + } + } + } } - - parent.push({ ...o, children: (levels[o.level] = []) }); }); - return result; + return ret; }; From ee98b6f858f986faaeb25f286978f7e30259aca7 Mon Sep 17 00:00:00 2001 From: fantasticit Date: Sun, 9 Apr 2023 12:15:05 +0800 Subject: [PATCH 2/7] close #242 --- packages/client/src/tiptap/core/wrappers/title/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/client/src/tiptap/core/wrappers/title/index.tsx b/packages/client/src/tiptap/core/wrappers/title/index.tsx index d7e36c6c..c750b861 100644 --- a/packages/client/src/tiptap/core/wrappers/title/index.tsx +++ b/packages/client/src/tiptap/core/wrappers/title/index.tsx @@ -5,7 +5,6 @@ import cls from 'classnames'; import { ImageUploader } from 'components/image-uploader'; import { useCallback, useEffect, useRef } from 'react'; import { createPortal } from 'react-dom'; -import { LazyLoadImage } from 'react-lazy-load-image-component'; import styles from './index.module.scss'; @@ -83,7 +82,7 @@ export const TitleWrapper = ({ editor, node }) => { {cover ? (
- + 请选择或移除封面 {isEditable ? (
From 4d505de16e82c432d219f4957ac6d5e618310972 Mon Sep 17 00:00:00 2001 From: fantasticit Date: Sun, 9 Apr 2023 12:15:41 +0800 Subject: [PATCH 3/7] fix jserror --- .../client/src/thirtypart/kityminder/kity-core/core/theme.js | 2 +- .../client/src/tiptap/core/bubble-menu/bubble-menu-plugin.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/theme.js b/packages/client/src/thirtypart/kityminder/kity-core/core/theme.js index 459493d3..2cad95a8 100644 --- a/packages/client/src/thirtypart/kityminder/kity-core/core/theme.js +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/theme.js @@ -61,7 +61,7 @@ define(function (require, exports, module) { }, setTheme: function (name) { - if (name && !_themes[name]) throw new Error('Theme ' + name + ' not exists!'); + if (name && !_themes[name]) return; var lastTheme = this._theme; this._theme = name || null; var container = this.getRenderTarget(); diff --git a/packages/client/src/tiptap/core/bubble-menu/bubble-menu-plugin.tsx b/packages/client/src/tiptap/core/bubble-menu/bubble-menu-plugin.tsx index 58c8833b..71c7027a 100644 --- a/packages/client/src/tiptap/core/bubble-menu/bubble-menu-plugin.tsx +++ b/packages/client/src/tiptap/core/bubble-menu/bubble-menu-plugin.tsx @@ -1,7 +1,7 @@ import { Editor, isNodeSelection, isTextSelection, posToDOMRect } from '@tiptap/core'; import { EditorState, Plugin, PluginKey } from 'prosemirror-state'; import { EditorView } from 'prosemirror-view'; -import tippy, { Instance, Props } from 'tippy.js'; +import tippy, { Instance, Props, sticky } from 'tippy.js'; export interface BubbleMenuPluginProps { pluginKey: PluginKey | string; @@ -144,6 +144,7 @@ export class BubbleMenuView { trigger: 'manual', placement: 'top', hideOnClick: 'toggle', + plugins: [sticky], ...Object.assign( { zIndex: 999, From 4eca3123226d865b7839898b879395f2f3edfaeb Mon Sep 17 00:00:00 2001 From: fantasticit Date: Sun, 9 Apr 2023 12:15:56 +0800 Subject: [PATCH 4/7] close #231 --- .../markdown-to-prosemirror/html-to-prosemirror/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/client/src/tiptap/markdown/markdown-to-prosemirror/html-to-prosemirror/index.ts b/packages/client/src/tiptap/markdown/markdown-to-prosemirror/html-to-prosemirror/index.ts index 60c840d2..e774bbd1 100644 --- a/packages/client/src/tiptap/markdown/markdown-to-prosemirror/html-to-prosemirror/index.ts +++ b/packages/client/src/tiptap/markdown/markdown-to-prosemirror/html-to-prosemirror/index.ts @@ -22,7 +22,9 @@ function fixNode(doc) { } if (node.type === 'tableRow') { - node.content = (node.content || []).filter((subNode) => subNode.type === 'tableCell'); + node.content = (node.content || []).filter( + (subNode) => subNode.type === 'tableCell' || subNode.type === 'tableHeader' + ); } if (node.type === 'tableCell') { From a6690f9edf2d8532020061e5a0e69311a5d6969a Mon Sep 17 00:00:00 2001 From: fantasticit Date: Sun, 9 Apr 2023 12:54:43 +0800 Subject: [PATCH 5/7] close #228 --- .../src/components/icons/IconLineHeight.tsx | 17 +++ .../client/src/components/icons/index.tsx | 1 + packages/client/src/tiptap/core/all-kit.ts | 2 + .../src/tiptap/core/extensions/heading.ts | 112 +++++++++++++++++- .../src/tiptap/core/extensions/indent.ts | 2 +- .../src/tiptap/core/extensions/line-height.ts | 58 +++++++++ .../src/tiptap/core/extensions/paragraph.ts | 64 +++++++++- .../src/tiptap/core/extensions/status.ts | 1 + .../tiptap/core/menus/lineheight/index.tsx | 48 ++++++++ .../collaboration/collaboration/menubar.tsx | 2 + .../src/tiptap/editor/collaboration/kit.ts | 2 + 11 files changed, 303 insertions(+), 6 deletions(-) create mode 100644 packages/client/src/components/icons/IconLineHeight.tsx create mode 100644 packages/client/src/tiptap/core/extensions/line-height.ts create mode 100644 packages/client/src/tiptap/core/menus/lineheight/index.tsx diff --git a/packages/client/src/components/icons/IconLineHeight.tsx b/packages/client/src/components/icons/IconLineHeight.tsx new file mode 100644 index 00000000..492905c6 --- /dev/null +++ b/packages/client/src/components/icons/IconLineHeight.tsx @@ -0,0 +1,17 @@ +import { Icon } from '@douyinfe/semi-ui'; + +export const IconLineHeight: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => { + return ( + + + + } + /> + ); +}; diff --git a/packages/client/src/components/icons/index.tsx b/packages/client/src/components/icons/index.tsx index 4c5cf48e..c4715c4c 100644 --- a/packages/client/src/components/icons/index.tsx +++ b/packages/client/src/components/icons/index.tsx @@ -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'; diff --git a/packages/client/src/tiptap/core/all-kit.ts b/packages/client/src/tiptap/core/all-kit.ts index d7fe2a2f..226505e2 100644 --- a/packages/client/src/tiptap/core/all-kit.ts +++ b/packages/client/src/tiptap/core/all-kit.ts @@ -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, diff --git a/packages/client/src/tiptap/core/extensions/heading.ts b/packages/client/src/tiptap/core/extensions/heading.ts index c163809c..26c2fadf 100644 --- a/packages/client/src/tiptap/core/extensions/heading.ts +++ b/packages/client/src/tiptap/core/extensions/heading.ts @@ -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; +} + +declare module '@tiptap/core' { + interface Commands { + heading: { + /** + * Set a heading node + */ + setHeading: (attributes: { level: Level }) => ReturnType; + /** + * Toggle a heading node + */ + toggleHeading: (attributes: { level: Level }) => ReturnType; + }; + } +} + +export const Heading = Node.create({ + 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, + }, + }); + }); + }, +}); diff --git a/packages/client/src/tiptap/core/extensions/indent.ts b/packages/client/src/tiptap/core/extensions/indent.ts index 519c1f40..44350cb4 100644 --- a/packages/client/src/tiptap/core/extensions/indent.ts +++ b/packages/client/src/tiptap/core/extensions/indent.ts @@ -92,7 +92,7 @@ export const Indent = Extension.create({ 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, }, diff --git a/packages/client/src/tiptap/core/extensions/line-height.ts b/packages/client/src/tiptap/core/extensions/line-height.ts new file mode 100644 index 00000000..b63abf77 --- /dev/null +++ b/packages/client/src/tiptap/core/extensions/line-height.ts @@ -0,0 +1,58 @@ +import { Extension } from '@tiptap/core'; + +declare module '@tiptap/core' { + interface Commands { + 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')); + }, + }; + }, +}); diff --git a/packages/client/src/tiptap/core/extensions/paragraph.ts b/packages/client/src/tiptap/core/extensions/paragraph.ts index bb06121e..56a28f00 100644 --- a/packages/client/src/tiptap/core/extensions/paragraph.ts +++ b/packages/client/src/tiptap/core/extensions/paragraph.ts @@ -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; +} + +declare module '@tiptap/core' { + interface Commands { + paragraph: { + /** + * Toggle a paragraph + */ + setParagraph: () => ReturnType; + }; + } +} + +export const Paragraph = Node.create({ + 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(), + }; + }, }); diff --git a/packages/client/src/tiptap/core/extensions/status.ts b/packages/client/src/tiptap/core/extensions/status.ts index 2edca440..43f655a0 100644 --- a/packages/client/src/tiptap/core/extensions/status.ts +++ b/packages/client/src/tiptap/core/extensions/status.ts @@ -24,6 +24,7 @@ export const Status = Node.create({ group: 'inline', inline: true, selectable: true, + draggable: true, atom: true, addAttributes() { diff --git a/packages/client/src/tiptap/core/menus/lineheight/index.tsx b/packages/client/src/tiptap/core/menus/lineheight/index.tsx new file mode 100644 index 00000000..68f1fb54 --- /dev/null +++ b/packages/client/src/tiptap/core/menus/lineheight/index.tsx @@ -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 ( + ( + toggle(val)}> + {val || '默认'} + + ))} + > + + +