mirror of https://github.com/fantasticit/think.git
feat: improve tiptap editor
This commit is contained in:
parent
2ad6518a51
commit
cd22f259fa
|
@ -33,6 +33,7 @@ import { OrderedList } from './extensions/orderedList';
|
||||||
import { Paragraph } from './extensions/paragraph';
|
import { Paragraph } from './extensions/paragraph';
|
||||||
import { Placeholder } from './extensions/placeholder';
|
import { Placeholder } from './extensions/placeholder';
|
||||||
import { SearchNReplace } from './extensions/search';
|
import { SearchNReplace } from './extensions/search';
|
||||||
|
import { SelectionExtension } from './extensions/selection';
|
||||||
import { Status } from './extensions/status';
|
import { Status } from './extensions/status';
|
||||||
import { Strike } from './extensions/strike';
|
import { Strike } from './extensions/strike';
|
||||||
import { Table } from './extensions/table';
|
import { Table } from './extensions/table';
|
||||||
|
@ -85,6 +86,7 @@ export const BaseKit = [
|
||||||
Paragraph,
|
Paragraph,
|
||||||
Placeholder,
|
Placeholder,
|
||||||
SearchNReplace,
|
SearchNReplace,
|
||||||
|
SelectionExtension,
|
||||||
Status,
|
Status,
|
||||||
Strike,
|
Strike,
|
||||||
Table,
|
Table,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Node, mergeAttributes } from '@tiptap/core';
|
import { Node, mergeAttributes } from '@tiptap/core';
|
||||||
import { ReactNodeViewRenderer } from '@tiptap/react';
|
import { ReactNodeViewRenderer } from '@tiptap/react';
|
||||||
import { AttachmentWrapper } from '../components/attachment';
|
import { AttachmentWrapper } from '../wrappers/attachment';
|
||||||
import { getDatasetAttribute } from '../services/dataset';
|
import { getDatasetAttribute } from '../services/dataset';
|
||||||
|
|
||||||
declare module '@tiptap/core' {
|
declare module '@tiptap/core' {
|
||||||
|
@ -68,7 +68,6 @@ export const Attachment = Node.create({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
addCommands() {
|
addCommands() {
|
||||||
return {
|
return {
|
||||||
setAttachment:
|
setAttachment:
|
||||||
|
@ -78,6 +77,7 @@ export const Attachment = Node.create({
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
addNodeView() {
|
addNodeView() {
|
||||||
return ReactNodeViewRenderer(AttachmentWrapper);
|
return ReactNodeViewRenderer(AttachmentWrapper);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Node, Command, mergeAttributes, wrappingInputRule } from '@tiptap/core';
|
import { Node, Command, mergeAttributes, wrappingInputRule } from '@tiptap/core';
|
||||||
import { ReactNodeViewRenderer } from '@tiptap/react';
|
import { ReactNodeViewRenderer } from '@tiptap/react';
|
||||||
import { BannerWrapper } from '../components/banner';
|
import { BannerWrapper } from '../wrappers/banner';
|
||||||
import { typesAvailable } from '../services/markdown/markdownToHTML/markdownBanner';
|
import { typesAvailable } from '../services/markdown/markdownToHTML/markdownBanner';
|
||||||
import { getDatasetAttribute } from '../services/dataset';
|
import { getDatasetAttribute } from '../services/dataset';
|
||||||
|
|
||||||
|
@ -54,7 +54,6 @@ export const Banner = Node.create({
|
||||||
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
||||||
},
|
},
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
addCommands() {
|
addCommands() {
|
||||||
return {
|
return {
|
||||||
setBanner:
|
setBanner:
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Node, textblockTypeInputRule, mergeAttributes } from '@tiptap/core';
|
||||||
import { Plugin, PluginKey, TextSelection } from 'prosemirror-state';
|
import { Plugin, PluginKey, TextSelection } from 'prosemirror-state';
|
||||||
import { ReactNodeViewRenderer } from '@tiptap/react';
|
import { ReactNodeViewRenderer } from '@tiptap/react';
|
||||||
import { LowlightPlugin } from '../services/lowlightPlugin';
|
import { LowlightPlugin } from '../services/lowlightPlugin';
|
||||||
import { CodeBlockWrapper } from '../components/codeBlock';
|
import { CodeBlockWrapper } from '../wrappers/codeBlock';
|
||||||
|
|
||||||
export interface CodeBlockOptions {
|
export interface CodeBlockOptions {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Node, mergeAttributes, wrappingInputRule } from '@tiptap/core';
|
import { Node, mergeAttributes, wrappingInputRule } from '@tiptap/core';
|
||||||
import { ReactNodeViewRenderer } from '@tiptap/react';
|
import { ReactNodeViewRenderer } from '@tiptap/react';
|
||||||
import { DocumentChildrenWrapper } from '../components/documentChildren';
|
import { DocumentChildrenWrapper } from '../wrappers/documentChildren';
|
||||||
import { getDatasetAttribute } from '../services/dataset';
|
import { getDatasetAttribute } from '../services/dataset';
|
||||||
|
|
||||||
declare module '@tiptap/core' {
|
declare module '@tiptap/core' {
|
||||||
|
@ -16,10 +16,16 @@ export const DocumentChildrenInputRegex = /^documentChildren\$$/;
|
||||||
export const DocumentChildren = Node.create({
|
export const DocumentChildren = Node.create({
|
||||||
name: 'documentChildren',
|
name: 'documentChildren',
|
||||||
group: 'block',
|
group: 'block',
|
||||||
draggable: true,
|
|
||||||
selectable: true,
|
|
||||||
atom: true,
|
atom: true,
|
||||||
|
|
||||||
|
addOptions() {
|
||||||
|
return {
|
||||||
|
HTMLAttributes: {
|
||||||
|
class: 'documentChildren',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
addAttributes() {
|
addAttributes() {
|
||||||
return {
|
return {
|
||||||
wikiId: {
|
wikiId: {
|
||||||
|
@ -32,18 +38,11 @@ export const DocumentChildren = Node.create({
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
addOptions() {
|
|
||||||
return {
|
|
||||||
HTMLAttributes: {
|
|
||||||
class: 'documentChildren',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
tag: 'div',
|
tag: 'div.documentChildren',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
@ -51,7 +50,7 @@ export const DocumentChildren = Node.create({
|
||||||
renderHTML({ HTMLAttributes }) {
|
renderHTML({ HTMLAttributes }) {
|
||||||
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
||||||
},
|
},
|
||||||
// @ts-ignore
|
|
||||||
addCommands() {
|
addCommands() {
|
||||||
return {
|
return {
|
||||||
setDocumentChildren:
|
setDocumentChildren:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Node, mergeAttributes, wrappingInputRule } from '@tiptap/core';
|
import { Node, mergeAttributes, wrappingInputRule } from '@tiptap/core';
|
||||||
import { ReactNodeViewRenderer } from '@tiptap/react';
|
import { ReactNodeViewRenderer } from '@tiptap/react';
|
||||||
import { DocumentReferenceWrapper } from '../components/documentReference';
|
import { DocumentReferenceWrapper } from '../wrappers/documentReference';
|
||||||
import { getDatasetAttribute } from '../services/dataset';
|
import { getDatasetAttribute } from '../services/dataset';
|
||||||
|
|
||||||
declare module '@tiptap/core' {
|
declare module '@tiptap/core' {
|
||||||
|
@ -16,9 +16,7 @@ export const DocumentReferenceInputRegex = /^documentReference\$$/;
|
||||||
export const DocumentReference = Node.create({
|
export const DocumentReference = Node.create({
|
||||||
name: 'documentReference',
|
name: 'documentReference',
|
||||||
group: 'block',
|
group: 'block',
|
||||||
draggable: true,
|
|
||||||
atom: true,
|
atom: true,
|
||||||
selectable: true,
|
|
||||||
|
|
||||||
addAttributes() {
|
addAttributes() {
|
||||||
return {
|
return {
|
||||||
|
@ -48,7 +46,7 @@ export const DocumentReference = Node.create({
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
tag: 'div',
|
tag: 'div.documentReference',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
@ -57,7 +55,6 @@ export const DocumentReference = Node.create({
|
||||||
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
||||||
},
|
},
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
addCommands() {
|
addCommands() {
|
||||||
return {
|
return {
|
||||||
setDocumentReference:
|
setDocumentReference:
|
||||||
|
|
|
@ -4,8 +4,8 @@ import { Plugin, PluginKey } from 'prosemirror-state';
|
||||||
import { Decoration, DecorationSet } from 'prosemirror-view';
|
import { Decoration, DecorationSet } from 'prosemirror-view';
|
||||||
import Suggestion from '@tiptap/suggestion';
|
import Suggestion from '@tiptap/suggestion';
|
||||||
import tippy from 'tippy.js';
|
import tippy from 'tippy.js';
|
||||||
import { EmojiList } from '../components/emojiList';
|
import { EmojiList } from '../wrappers/emojiList';
|
||||||
import { emojiSearch, emojisToName } from '../components/emojiList/emojis';
|
import { emojiSearch, emojisToName } from '../wrappers/emojiList/emojis';
|
||||||
|
|
||||||
declare module '@tiptap/core' {
|
declare module '@tiptap/core' {
|
||||||
interface Commands<ReturnType> {
|
interface Commands<ReturnType> {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Plugin, PluginKey } from 'prosemirror-state';
|
||||||
import { Decoration, DecorationSet } from 'prosemirror-view';
|
import { Decoration, DecorationSet } from 'prosemirror-view';
|
||||||
import Suggestion from '@tiptap/suggestion';
|
import Suggestion from '@tiptap/suggestion';
|
||||||
import tippy from 'tippy.js';
|
import tippy from 'tippy.js';
|
||||||
import { MenuList } from '../components/menuList';
|
import { MenuList } from '../wrappers/menuList';
|
||||||
import { EVOKE_MENU_ITEMS } from '../menus/evokeMenu';
|
import { EVOKE_MENU_ITEMS } from '../menus/evokeMenu';
|
||||||
|
|
||||||
export const EvokeMenuPluginKey = new PluginKey('evokeMenu');
|
export const EvokeMenuPluginKey = new PluginKey('evokeMenu');
|
||||||
|
|
|
@ -16,6 +16,7 @@ declare module '@tiptap/core' {
|
||||||
export const HorizontalRule = Node.create<HorizontalRuleOptions>({
|
export const HorizontalRule = Node.create<HorizontalRuleOptions>({
|
||||||
name: 'horizontalRule',
|
name: 'horizontalRule',
|
||||||
group: 'block',
|
group: 'block',
|
||||||
|
selectable: true,
|
||||||
|
|
||||||
addOptions() {
|
addOptions() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Node, mergeAttributes } from '@tiptap/core';
|
import { Node, mergeAttributes } from '@tiptap/core';
|
||||||
import { ReactNodeViewRenderer } from '@tiptap/react';
|
import { ReactNodeViewRenderer } from '@tiptap/react';
|
||||||
import { IframeWrapper } from '../components/iframe';
|
import { IframeWrapper } from '../wrappers/iframe';
|
||||||
import { getDatasetAttribute } from '../services/dataset';
|
import { getDatasetAttribute } from '../services/dataset';
|
||||||
|
|
||||||
declare module '@tiptap/core' {
|
declare module '@tiptap/core' {
|
||||||
|
@ -16,7 +16,7 @@ export const Iframe = Node.create({
|
||||||
content: '',
|
content: '',
|
||||||
marks: '',
|
marks: '',
|
||||||
group: 'block',
|
group: 'block',
|
||||||
draggable: true,
|
selectable: true,
|
||||||
atom: true,
|
atom: true,
|
||||||
|
|
||||||
addOptions() {
|
addOptions() {
|
||||||
|
@ -56,7 +56,6 @@ export const Iframe = Node.create({
|
||||||
return ['iframe', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
return ['iframe', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
||||||
},
|
},
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
addCommands() {
|
addCommands() {
|
||||||
return {
|
return {
|
||||||
setIframe:
|
setIframe:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Image as BuiltInImage } from '@tiptap/extension-image';
|
import { Image as BuiltInImage } from '@tiptap/extension-image';
|
||||||
import { ReactNodeViewRenderer } from '@tiptap/react';
|
import { ReactNodeViewRenderer } from '@tiptap/react';
|
||||||
import { ImageWrapper } from '../components/image';
|
import { ImageWrapper } from '../wrappers/image';
|
||||||
|
|
||||||
const resolveImageEl = (element) => (element.nodeName === 'IMG' ? element : element.querySelector('img'));
|
const resolveImageEl = (element) => (element.nodeName === 'IMG' ? element : element.querySelector('img'));
|
||||||
|
|
||||||
|
@ -20,10 +20,12 @@ export const Image = BuiltInImage.extend({
|
||||||
content: '',
|
content: '',
|
||||||
marks: '',
|
marks: '',
|
||||||
group: 'block',
|
group: 'block',
|
||||||
draggable: true,
|
draggable: false,
|
||||||
|
selectable: true,
|
||||||
atom: true,
|
atom: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
addAttributes() {
|
addAttributes() {
|
||||||
return {
|
return {
|
||||||
...this.parent?.(),
|
...this.parent?.(),
|
||||||
|
@ -59,6 +61,7 @@ export const Image = BuiltInImage.extend({
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
addCommands() {
|
addCommands() {
|
||||||
return {
|
return {
|
||||||
...this.parent?.(),
|
...this.parent?.(),
|
||||||
|
@ -69,6 +72,7 @@ export const Image = BuiltInImage.extend({
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
addNodeView() {
|
addNodeView() {
|
||||||
return ReactNodeViewRenderer(ImageWrapper);
|
return ReactNodeViewRenderer(ImageWrapper);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Node, mergeAttributes, nodeInputRule } from '@tiptap/core';
|
import { Node, mergeAttributes, nodeInputRule } from '@tiptap/core';
|
||||||
import { ReactNodeViewRenderer } from '@tiptap/react';
|
import { ReactNodeViewRenderer } from '@tiptap/react';
|
||||||
import { KatexWrapper } from '../components/katex';
|
import { KatexWrapper } from '../wrappers/katex';
|
||||||
|
|
||||||
declare module '@tiptap/core' {
|
declare module '@tiptap/core' {
|
||||||
interface Commands<ReturnType> {
|
interface Commands<ReturnType> {
|
||||||
|
@ -46,11 +46,10 @@ export const Katex = Node.create({
|
||||||
return ['span', mergeAttributes((this.options && this.options.HTMLAttributes) || {}, HTMLAttributes)];
|
return ['span', mergeAttributes((this.options && this.options.HTMLAttributes) || {}, HTMLAttributes)];
|
||||||
},
|
},
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
addCommands() {
|
addCommands() {
|
||||||
return {
|
return {
|
||||||
setKatex:
|
setKatex:
|
||||||
(options) =>
|
(options = {}) =>
|
||||||
({ commands }) => {
|
({ commands }) => {
|
||||||
return commands.insertContent({
|
return commands.insertContent({
|
||||||
type: this.name,
|
type: this.name,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Node } from '@tiptap/core';
|
import { Node } from '@tiptap/core';
|
||||||
import { ReactNodeViewRenderer } from '@tiptap/react';
|
import { ReactNodeViewRenderer } from '@tiptap/react';
|
||||||
import { LoadingWrapper } from '../components/loading';
|
import { LoadingWrapper } from '../wrappers/loading';
|
||||||
|
|
||||||
export const Loading = Node.create({
|
export const Loading = Node.create({
|
||||||
name: 'loading',
|
name: 'loading',
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { Node, mergeAttributes, nodeInputRule } from '@tiptap/core';
|
import { Node, mergeAttributes, nodeInputRule } from '@tiptap/core';
|
||||||
import { ReactNodeViewRenderer } from '@tiptap/react';
|
import { ReactNodeViewRenderer } from '@tiptap/react';
|
||||||
import { safeJSONParse } from 'helpers/json';
|
import { MindWrapper } from '../wrappers/mind';
|
||||||
import { MindWrapper } from '../components/mind';
|
|
||||||
import { getDatasetAttribute } from '../services/dataset';
|
import { getDatasetAttribute } from '../services/dataset';
|
||||||
|
|
||||||
const DEFAULT_MIND_DATA = {
|
const DEFAULT_MIND_DATA = {
|
||||||
|
@ -27,7 +26,7 @@ export const Mind = Node.create({
|
||||||
content: '',
|
content: '',
|
||||||
marks: '',
|
marks: '',
|
||||||
group: 'block',
|
group: 'block',
|
||||||
draggable: true,
|
selectable: true,
|
||||||
atom: true,
|
atom: true,
|
||||||
|
|
||||||
addAttributes() {
|
addAttributes() {
|
||||||
|
@ -67,7 +66,6 @@ export const Mind = Node.create({
|
||||||
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
||||||
},
|
},
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
addCommands() {
|
addCommands() {
|
||||||
return {
|
return {
|
||||||
setMind:
|
setMind:
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { Extension } from '@tiptap/core';
|
||||||
|
import { Plugin, PluginKey, NodeSelection, TextSelection, Selection, AllSelection } from 'prosemirror-state';
|
||||||
|
import { Decoration, DecorationSet } from 'prosemirror-view';
|
||||||
|
import { EXTENSION_PRIORITY_HIGHEST } from '../constants';
|
||||||
|
|
||||||
|
export const selectionPluginKey = new PluginKey('selection');
|
||||||
|
|
||||||
|
export const getTopLevelNodesFromSelection = (selection: Selection, doc) => {
|
||||||
|
const nodes: { node; pos: number }[] = [];
|
||||||
|
if (selection.from !== selection.to) {
|
||||||
|
const { from, to } = selection;
|
||||||
|
doc.nodesBetween(from, to, (node, pos) => {
|
||||||
|
const withinSelection = from <= pos && pos + node.nodeSize <= to;
|
||||||
|
if (node && node.type.name !== 'paragraph' && !node.isText && withinSelection) {
|
||||||
|
nodes.push({ node, pos });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDecorations = (doc, selection: Selection): DecorationSet => {
|
||||||
|
if (selection instanceof NodeSelection) {
|
||||||
|
return DecorationSet.create(doc, [
|
||||||
|
Decoration.node(selection.from, selection.to, {
|
||||||
|
class: 'selected-node',
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (selection instanceof TextSelection || selection instanceof AllSelection) {
|
||||||
|
const decorations = getTopLevelNodesFromSelection(selection, doc).map(({ node, pos }) => {
|
||||||
|
return Decoration.node(pos, pos + node.nodeSize, {
|
||||||
|
class: 'selected-node',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return DecorationSet.create(doc, decorations);
|
||||||
|
}
|
||||||
|
return DecorationSet.empty;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SelectionExtension = Extension.create({
|
||||||
|
name: 'selection',
|
||||||
|
priority: EXTENSION_PRIORITY_HIGHEST,
|
||||||
|
addProseMirrorPlugins() {
|
||||||
|
return [
|
||||||
|
new Plugin({
|
||||||
|
key: selectionPluginKey,
|
||||||
|
props: {
|
||||||
|
decorations(state) {
|
||||||
|
const { doc, selection } = state;
|
||||||
|
const decorationSet = getDecorations(doc, selection);
|
||||||
|
return decorationSet;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,6 +1,6 @@
|
||||||
import { Node, mergeAttributes } from '@tiptap/core';
|
import { Node, mergeAttributes } from '@tiptap/core';
|
||||||
import { ReactNodeViewRenderer } from '@tiptap/react';
|
import { ReactNodeViewRenderer } from '@tiptap/react';
|
||||||
import { StatusWrapper } from '../components/status';
|
import { StatusWrapper } from '../wrappers/status';
|
||||||
import { getDatasetAttribute } from '../services/dataset';
|
import { getDatasetAttribute } from '../services/dataset';
|
||||||
|
|
||||||
declare module '@tiptap/core' {
|
declare module '@tiptap/core' {
|
||||||
|
@ -41,20 +41,19 @@ export const Status = Node.create({
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
tag: 'div',
|
tag: 'span.status',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
renderHTML({ HTMLAttributes }) {
|
renderHTML({ HTMLAttributes }) {
|
||||||
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
return ['span', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
||||||
},
|
},
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
addCommands() {
|
addCommands() {
|
||||||
return {
|
return {
|
||||||
setStatus:
|
setStatus:
|
||||||
(options) =>
|
(options = {}) =>
|
||||||
({ commands }) => {
|
({ commands }) => {
|
||||||
return commands.insertContent({
|
return commands.insertContent({
|
||||||
type: this.name,
|
type: this.name,
|
||||||
|
|
|
@ -40,17 +40,18 @@ export const Table = BuiltInTable.extend({
|
||||||
if (fixedWidth && totalWidth > 0) {
|
if (fixedWidth && totalWidth > 0) {
|
||||||
HTMLAttributes.style = `width: ${totalWidth}px;`;
|
HTMLAttributes.style = `width: ${totalWidth}px;`;
|
||||||
} else if (totalWidth && totalWidth > 0) {
|
} else if (totalWidth && totalWidth > 0) {
|
||||||
HTMLAttributes.style = `min-width: 100%`;
|
HTMLAttributes.style = `min-width: ${totalWidth}px`;
|
||||||
} else {
|
} else {
|
||||||
HTMLAttributes.style = null;
|
HTMLAttributes.style = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'div',
|
'div',
|
||||||
{ class: 'tableWrapper' },
|
{ class: 'tableWrapper adas' },
|
||||||
['table', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), ['tbody', 0]],
|
['table', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), ['tbody', 0]],
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
}).configure({
|
}).configure({
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
cellMinWidth: 50,
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { TaskItem as BuiltInTaskItem } from '@tiptap/extension-task-item';
|
||||||
import { Plugin } from 'prosemirror-state';
|
import { Plugin } from 'prosemirror-state';
|
||||||
import { findParentNodeClosestToPos } from 'prosemirror-utils';
|
import { findParentNodeClosestToPos } from 'prosemirror-utils';
|
||||||
import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants';
|
import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants';
|
||||||
import { TaskItemWrapper } from '../components/taskItem';
|
import { TaskItemWrapper } from '../wrappers/taskItem';
|
||||||
|
|
||||||
const CustomTaskItem = BuiltInTaskItem.extend({
|
const CustomTaskItem = BuiltInTaskItem.extend({
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
|
|
|
@ -3,10 +3,10 @@ import { Space, Button } from '@douyinfe/semi-ui';
|
||||||
import { IconUndo, IconRedo } from '@douyinfe/semi-icons';
|
import { IconUndo, IconRedo } from '@douyinfe/semi-icons';
|
||||||
import { Tooltip } from 'components/tooltip';
|
import { Tooltip } from 'components/tooltip';
|
||||||
import { IconClear } from 'components/icons';
|
import { IconClear } from 'components/icons';
|
||||||
import { Divider } from './components/divider';
|
import { Divider } from './wrappers/divider';
|
||||||
import { MediaInsertMenu } from './menus/mediaInsert';
|
import { MediaInsertMenu } from './menus/mediaInsert';
|
||||||
import { Paragraph } from './menus/components/paragraph';
|
import { Paragraph } from './menus/paragraph';
|
||||||
import { FontSize } from './menus/components/fontSize';
|
import { FontSize } from './menus/fontSize';
|
||||||
import { BaseMenu } from './menus/baseMenu';
|
import { BaseMenu } from './menus/baseMenu';
|
||||||
import { AlignMenu } from './menus/align';
|
import { AlignMenu } from './menus/align';
|
||||||
import { ListMenu } from './menus/list';
|
import { ListMenu } from './menus/list';
|
||||||
|
|
|
@ -29,12 +29,15 @@ export const AlignMenu = ({ editor }) => {
|
||||||
<Tooltip content="左对齐">
|
<Tooltip content="左对齐">
|
||||||
<Button onClick={toggle('left')} icon={<IconAlignLeft />} type="tertiary" theme="borderless" />
|
<Button onClick={toggle('left')} icon={<IconAlignLeft />} type="tertiary" theme="borderless" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip content="居中">
|
<Tooltip content="居中">
|
||||||
<Button onClick={toggle('center')} icon={<IconAlignCenter />} type="tertiary" theme="borderless" />
|
<Button onClick={toggle('center')} icon={<IconAlignCenter />} type="tertiary" theme="borderless" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip content="右对齐">
|
<Tooltip content="右对齐">
|
||||||
<Button onClick={toggle('right')} icon={<IconAlignRight />} type="tertiary" theme="borderless" />
|
<Button onClick={toggle('right')} icon={<IconAlignRight />} type="tertiary" theme="borderless" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip content="两端对齐">
|
<Tooltip content="两端对齐">
|
||||||
<Button onClick={toggle('justify')} icon={<IconAlignJustify />} type="tertiary" theme="borderless" />
|
<Button onClick={toggle('justify')} icon={<IconAlignJustify />} type="tertiary" theme="borderless" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Space, Button } from '@douyinfe/semi-ui';
|
import { Space, Button } from '@douyinfe/semi-ui';
|
||||||
import { IconDelete, IconTickCircle, IconAlertTriangle, IconClear, IconInfoCircle } from '@douyinfe/semi-icons';
|
import { IconDelete, IconTickCircle, IconAlertTriangle, IconClear, IconInfoCircle } from '@douyinfe/semi-icons';
|
||||||
import { Tooltip } from 'components/tooltip';
|
import { Tooltip } from 'components/tooltip';
|
||||||
import { BubbleMenu } from './components/bubbleMenu';
|
import { BubbleMenu } from '../views/bubbleMenu';
|
||||||
import { Divider } from '../components/divider';
|
import { Divider } from '../wrappers/divider';
|
||||||
import { Banner } from '../extensions/banner';
|
import { Banner } from '../extensions/banner';
|
||||||
import { deleteNode } from '../services/deleteNode';
|
import { deleteNode } from '../services/deleteNode';
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Button } from '@douyinfe/semi-ui';
|
||||||
import { Tooltip } from 'components/tooltip';
|
import { Tooltip } from 'components/tooltip';
|
||||||
import { IconQuote, IconLink, IconHorizontalRule } from 'components/icons';
|
import { IconQuote, IconLink, IconHorizontalRule } from 'components/icons';
|
||||||
import { isTitleActive } from '../services/isActive';
|
import { isTitleActive } from '../services/isActive';
|
||||||
import { Emoji } from './components/emoji';
|
import { Emoji } from './emoji';
|
||||||
import { Search } from './search';
|
import { Search } from './search';
|
||||||
|
|
||||||
export const BaseInsertMenu: React.FC<{ editor: any }> = ({ editor }) => {
|
export const BaseInsertMenu: React.FC<{ editor: any }> = ({ editor }) => {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Button } from '@douyinfe/semi-ui';
|
||||||
import { IconFont, IconMark } from '@douyinfe/semi-icons';
|
import { IconFont, IconMark } from '@douyinfe/semi-icons';
|
||||||
import { Tooltip } from 'components/tooltip';
|
import { Tooltip } from 'components/tooltip';
|
||||||
import { isTitleActive } from '../services/isActive';
|
import { isTitleActive } from '../services/isActive';
|
||||||
import { Color } from './components/color';
|
import { ColorPicker } from './colorPicker';
|
||||||
|
|
||||||
export const ColorMenu: React.FC<{ editor: any }> = ({ editor }) => {
|
export const ColorMenu: React.FC<{ editor: any }> = ({ editor }) => {
|
||||||
const { color, backgroundColor } = editor.getAttributes('textStyle');
|
const { color, backgroundColor } = editor.getAttributes('textStyle');
|
||||||
|
@ -14,7 +14,7 @@ export const ColorMenu: React.FC<{ editor: any }> = ({ editor }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Color
|
<ColorPicker
|
||||||
onSetColor={(color) => {
|
onSetColor={(color) => {
|
||||||
editor.chain().focus().setColor(color).run();
|
editor.chain().focus().setColor(color).run();
|
||||||
}}
|
}}
|
||||||
|
@ -45,8 +45,9 @@ export const ColorMenu: React.FC<{ editor: any }> = ({ editor }) => {
|
||||||
disabled={isTitleActive(editor)}
|
disabled={isTitleActive(editor)}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Color>
|
</ColorPicker>
|
||||||
<Color
|
|
||||||
|
<ColorPicker
|
||||||
onSetColor={(color) => {
|
onSetColor={(color) => {
|
||||||
editor.chain().focus().setBackgroundColor(color).run();
|
editor.chain().focus().setBackgroundColor(color).run();
|
||||||
}}
|
}}
|
||||||
|
@ -71,7 +72,7 @@ export const ColorMenu: React.FC<{ editor: any }> = ({ editor }) => {
|
||||||
disabled={isTitleActive(editor)}
|
disabled={isTitleActive(editor)}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Color>
|
</ColorPicker>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,7 +26,7 @@ const colors = [
|
||||||
'rgb(234, 230, 255)',
|
'rgb(234, 230, 255)',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const Color: React.FC<{
|
export const ColorPicker: React.FC<{
|
||||||
onSetColor;
|
onSetColor;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}> = ({ children, onSetColor, disabled = false }) => {
|
}> = ({ children, onSetColor, disabled = false }) => {
|
|
@ -31,6 +31,7 @@ export const EVOKE_MENU_ITEMS = [
|
||||||
),
|
),
|
||||||
command: (editor: Editor) => editor.chain().focus().toggleHeading({ level: 1 }).run(),
|
command: (editor: Editor) => editor.chain().focus().toggleHeading({ level: 1 }).run(),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: '标题2',
|
key: '标题2',
|
||||||
label: (
|
label: (
|
||||||
|
@ -41,6 +42,7 @@ export const EVOKE_MENU_ITEMS = [
|
||||||
),
|
),
|
||||||
command: (editor: Editor) => editor.chain().focus().toggleHeading({ level: 2 }).run(),
|
command: (editor: Editor) => editor.chain().focus().toggleHeading({ level: 2 }).run(),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: '标题1',
|
key: '标题1',
|
||||||
label: (
|
label: (
|
||||||
|
@ -51,6 +53,7 @@ export const EVOKE_MENU_ITEMS = [
|
||||||
),
|
),
|
||||||
command: (editor: Editor) => editor.chain().focus().toggleHeading({ level: 3 }).run(),
|
command: (editor: Editor) => editor.chain().focus().toggleHeading({ level: 3 }).run(),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: '无序列表',
|
key: '无序列表',
|
||||||
label: (
|
label: (
|
||||||
|
@ -61,6 +64,7 @@ export const EVOKE_MENU_ITEMS = [
|
||||||
),
|
),
|
||||||
command: (editor: Editor) => editor.chain().focus().toggleBulletList().run(),
|
command: (editor: Editor) => editor.chain().focus().toggleBulletList().run(),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: '有序列表',
|
key: '有序列表',
|
||||||
label: (
|
label: (
|
||||||
|
@ -71,6 +75,7 @@ export const EVOKE_MENU_ITEMS = [
|
||||||
),
|
),
|
||||||
command: (editor: Editor) => editor.chain().focus().toggleOrderedList().run(),
|
command: (editor: Editor) => editor.chain().focus().toggleOrderedList().run(),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: '任务列表',
|
key: '任务列表',
|
||||||
label: (
|
label: (
|
||||||
|
@ -81,6 +86,7 @@ export const EVOKE_MENU_ITEMS = [
|
||||||
),
|
),
|
||||||
command: (editor: Editor) => editor.chain().focus().toggleTaskList().run(),
|
command: (editor: Editor) => editor.chain().focus().toggleTaskList().run(),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: '链接',
|
key: '链接',
|
||||||
label: (
|
label: (
|
||||||
|
@ -91,6 +97,7 @@ export const EVOKE_MENU_ITEMS = [
|
||||||
),
|
),
|
||||||
command: (editor: Editor) => editor.chain().focus().toggleLink({ href: '' }).run(),
|
command: (editor: Editor) => editor.chain().focus().toggleLink({ href: '' }).run(),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: '引用',
|
key: '引用',
|
||||||
label: (
|
label: (
|
||||||
|
@ -101,6 +108,7 @@ export const EVOKE_MENU_ITEMS = [
|
||||||
),
|
),
|
||||||
command: (editor: Editor) => editor.chain().focus().toggleBlockquote().run(),
|
command: (editor: Editor) => editor.chain().focus().toggleBlockquote().run(),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: '分割线',
|
key: '分割线',
|
||||||
label: (
|
label: (
|
||||||
|
@ -111,6 +119,7 @@ export const EVOKE_MENU_ITEMS = [
|
||||||
),
|
),
|
||||||
command: (editor: Editor) => editor.chain().focus().setHorizontalRule().run(),
|
command: (editor: Editor) => editor.chain().focus().setHorizontalRule().run(),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: '表格',
|
key: '表格',
|
||||||
label: (
|
label: (
|
||||||
|
@ -121,6 +130,7 @@ export const EVOKE_MENU_ITEMS = [
|
||||||
),
|
),
|
||||||
command: (editor: Editor) => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(),
|
command: (editor: Editor) => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: '代码块',
|
key: '代码块',
|
||||||
label: (
|
label: (
|
||||||
|
@ -131,6 +141,7 @@ export const EVOKE_MENU_ITEMS = [
|
||||||
),
|
),
|
||||||
command: (editor: Editor) => editor.chain().focus().toggleCodeBlock().run(),
|
command: (editor: Editor) => editor.chain().focus().toggleCodeBlock().run(),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: '图片',
|
key: '图片',
|
||||||
label: () => (
|
label: () => (
|
||||||
|
@ -141,6 +152,7 @@ export const EVOKE_MENU_ITEMS = [
|
||||||
),
|
),
|
||||||
command: (editor: Editor) => editor.chain().focus().setEmptyImage().run(),
|
command: (editor: Editor) => editor.chain().focus().setEmptyImage().run(),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: '附件',
|
key: '附件',
|
||||||
label: () => (
|
label: () => (
|
||||||
|
@ -151,6 +163,7 @@ export const EVOKE_MENU_ITEMS = [
|
||||||
),
|
),
|
||||||
command: (editor: Editor) => editor.chain().focus().setAttachment().run(),
|
command: (editor: Editor) => editor.chain().focus().setAttachment().run(),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: '外链',
|
key: '外链',
|
||||||
label: (
|
label: (
|
||||||
|
@ -161,6 +174,7 @@ export const EVOKE_MENU_ITEMS = [
|
||||||
),
|
),
|
||||||
command: (editor: Editor) => editor.chain().focus().setIframe({ url: '' }).run(),
|
command: (editor: Editor) => editor.chain().focus().setIframe({ url: '' }).run(),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: '思维导图',
|
key: '思维导图',
|
||||||
label: (
|
label: (
|
||||||
|
@ -171,6 +185,7 @@ export const EVOKE_MENU_ITEMS = [
|
||||||
),
|
),
|
||||||
command: (editor: Editor) => editor.chain().focus().setMind().run(),
|
command: (editor: Editor) => editor.chain().focus().setMind().run(),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: '数学公式',
|
key: '数学公式',
|
||||||
label: (
|
label: (
|
||||||
|
@ -181,6 +196,7 @@ export const EVOKE_MENU_ITEMS = [
|
||||||
),
|
),
|
||||||
command: (editor: Editor) => editor.chain().focus().setKatex().run(),
|
command: (editor: Editor) => editor.chain().focus().setKatex().run(),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: '状态',
|
key: '状态',
|
||||||
label: (
|
label: (
|
||||||
|
@ -191,6 +207,7 @@ export const EVOKE_MENU_ITEMS = [
|
||||||
),
|
),
|
||||||
command: (editor: Editor) => editor.chain().focus().setStatus().run(),
|
command: (editor: Editor) => editor.chain().focus().setStatus().run(),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: '信息框',
|
key: '信息框',
|
||||||
label: (
|
label: (
|
||||||
|
@ -201,6 +218,7 @@ export const EVOKE_MENU_ITEMS = [
|
||||||
),
|
),
|
||||||
command: (editor: Editor) => editor.chain().focus().setBanner({ type: 'info' }).run(),
|
command: (editor: Editor) => editor.chain().focus().setBanner({ type: 'info' }).run(),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: '文档',
|
key: '文档',
|
||||||
label: (
|
label: (
|
||||||
|
@ -211,6 +229,7 @@ export const EVOKE_MENU_ITEMS = [
|
||||||
),
|
),
|
||||||
command: (editor: Editor) => editor.chain().focus().setDocumentReference().run(),
|
command: (editor: Editor) => editor.chain().focus().setDocumentReference().run(),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: '子文档',
|
key: '子文档',
|
||||||
label: (
|
label: (
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { Select } from '@douyinfe/semi-ui';
|
import { Select } from '@douyinfe/semi-ui';
|
||||||
import { isTitleActive } from '../../services/isActive';
|
import { isTitleActive } from '../services/isActive';
|
||||||
|
|
||||||
export const FONT_SIZES = [12, 13, 14, 15, 16, 19, 22, 24, 29, 32, 40, 48];
|
export const FONT_SIZES = [12, 13, 14, 15, 16, 19, 22, 24, 29, 32, 40, 48];
|
||||||
|
|
|
@ -3,8 +3,8 @@ import { Space, Button, InputNumber, Typography } from '@douyinfe/semi-ui';
|
||||||
import { IconAlignLeft, IconAlignCenter, IconAlignRight, IconUpload, IconDelete } from '@douyinfe/semi-icons';
|
import { IconAlignLeft, IconAlignCenter, IconAlignRight, IconUpload, IconDelete } from '@douyinfe/semi-icons';
|
||||||
import { Tooltip } from 'components/tooltip';
|
import { Tooltip } from 'components/tooltip';
|
||||||
import { Upload } from 'components/upload';
|
import { Upload } from 'components/upload';
|
||||||
import { BubbleMenu } from './components/bubbleMenu';
|
import { BubbleMenu } from '../views/bubbleMenu';
|
||||||
import { Divider } from '../components/divider';
|
import { Divider } from '../wrappers/divider';
|
||||||
import { Image } from '../extensions/image';
|
import { Image } from '../extensions/image';
|
||||||
import { getImageOriginSize } from '../services/image';
|
import { getImageOriginSize } from '../services/image';
|
||||||
|
|
||||||
|
@ -51,6 +51,7 @@ export const ImageBubbleMenu = ({ editor }) => {
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip content="居中">
|
<Tooltip content="居中">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -69,6 +70,7 @@ export const ImageBubbleMenu = ({ editor }) => {
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip content="右对齐">
|
<Tooltip content="右对齐">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -87,7 +89,9 @@ export const ImageBubbleMenu = ({ editor }) => {
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<Text>宽</Text>
|
<Text>宽</Text>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -106,6 +110,7 @@ export const ImageBubbleMenu = ({ editor }) => {
|
||||||
.run();
|
.run();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Text>高</Text>
|
<Text>高</Text>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -124,30 +129,9 @@ export const ImageBubbleMenu = ({ editor }) => {
|
||||||
.run();
|
.run();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
<Upload
|
|
||||||
accept="image/*"
|
|
||||||
onOK={async (url, fileName) => {
|
|
||||||
const { width, height } = await getImageOriginSize(url);
|
|
||||||
editor
|
|
||||||
.chain()
|
|
||||||
.updateAttributes(Image.name, {
|
|
||||||
src: url,
|
|
||||||
alt: fileName,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
})
|
|
||||||
.setNodeSelection(editor.state.selection.from)
|
|
||||||
.focus()
|
|
||||||
.run();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{() => (
|
|
||||||
<Tooltip content="上传图片">
|
|
||||||
<Button size="small" type="tertiary" theme="borderless" icon={<IconUpload />} />
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</Upload>
|
|
||||||
<Tooltip content="删除" hideOnClick>
|
<Tooltip content="删除" hideOnClick>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
|
||||||
import { Space, Button, Input } from '@douyinfe/semi-ui';
|
import { Space, Button, Input } from '@douyinfe/semi-ui';
|
||||||
import { IconExternalOpen, IconUnlink, IconTickCircle } from '@douyinfe/semi-icons';
|
import { IconExternalOpen, IconUnlink, IconTickCircle } from '@douyinfe/semi-icons';
|
||||||
import { Tooltip } from 'components/tooltip';
|
import { Tooltip } from 'components/tooltip';
|
||||||
import { BubbleMenu } from './components/bubbleMenu';
|
import { BubbleMenu } from '../views/bubbleMenu';
|
||||||
import { Link } from '../extensions/link';
|
import { Link } from '../extensions/link';
|
||||||
|
|
||||||
export const LinkBubbleMenu = ({ editor }) => {
|
export const LinkBubbleMenu = ({ editor }) => {
|
||||||
|
@ -28,11 +28,11 @@ export const LinkBubbleMenu = ({ editor }) => {
|
||||||
onChange={(v) => setUrl(v)}
|
onChange={(v) => setUrl(v)}
|
||||||
placeholder={'输入链接'}
|
placeholder={'输入链接'}
|
||||||
onEnterPress={(e) => {
|
onEnterPress={(e) => {
|
||||||
// @ts-ignore
|
const url = (e.target as HTMLInputElement).value;
|
||||||
const url = e.target.value;
|
|
||||||
setUrl(url);
|
setUrl(url);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Tooltip content="设置链接">
|
<Tooltip content="设置链接">
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -51,6 +51,7 @@ export const LinkBubbleMenu = ({ editor }) => {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip content="去除链接">
|
<Tooltip content="去除链接">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -62,6 +63,7 @@ export const LinkBubbleMenu = ({ editor }) => {
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip content="访问链接">
|
<Tooltip content="访问链接">
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
|
|
|
@ -50,7 +50,7 @@ export const MediaInsertMenu: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Dropdown.Item onClick={() => editor.chain().focus().toggleCodeBlock().run()}>
|
<Dropdown.Item>
|
||||||
<IconTable /> 表格
|
<IconTable /> 表格
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
@ -63,6 +63,7 @@ export const MediaInsertMenu: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||||
<IconImage />
|
<IconImage />
|
||||||
图片
|
图片
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
|
|
||||||
<Dropdown.Item onClick={() => editor.chain().focus().setAttachment().run()}>
|
<Dropdown.Item onClick={() => editor.chain().focus().setAttachment().run()}>
|
||||||
<IconAttachment />
|
<IconAttachment />
|
||||||
附件
|
附件
|
||||||
|
@ -71,6 +72,7 @@ export const MediaInsertMenu: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||||
<Dropdown.Item onClick={() => editor.chain().focus().setIframe({ url: '' }).run()}>
|
<Dropdown.Item onClick={() => editor.chain().focus().setIframe({ url: '' }).run()}>
|
||||||
<IconLink /> 外链
|
<IconLink /> 外链
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
|
|
||||||
<Dropdown.Item onClick={() => editor.chain().focus().setMind().run()}>
|
<Dropdown.Item onClick={() => editor.chain().focus().setMind().run()}>
|
||||||
<IconMind /> 思维导图
|
<IconMind /> 思维导图
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
|
@ -81,18 +83,22 @@ export const MediaInsertMenu: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||||
|
|
||||||
<Dropdown.Divider />
|
<Dropdown.Divider />
|
||||||
<Dropdown.Title>卡片</Dropdown.Title>
|
<Dropdown.Title>卡片</Dropdown.Title>
|
||||||
|
|
||||||
<Dropdown.Item onClick={() => editor.chain().focus().setStatus().run()}>
|
<Dropdown.Item onClick={() => editor.chain().focus().setStatus().run()}>
|
||||||
<IconStatus /> 状态
|
<IconStatus /> 状态
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
|
|
||||||
<Dropdown.Item onClick={() => editor.chain().focus().setBanner({ type: 'info' }).run()}>
|
<Dropdown.Item onClick={() => editor.chain().focus().setBanner({ type: 'info' }).run()}>
|
||||||
<IconInfo /> 信息框
|
<IconInfo /> 信息框
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
|
|
||||||
<Dropdown.Divider />
|
<Dropdown.Divider />
|
||||||
<Dropdown.Title>文档</Dropdown.Title>
|
<Dropdown.Title>文档</Dropdown.Title>
|
||||||
|
|
||||||
<Dropdown.Item onClick={() => editor.chain().focus().setDocumentReference().run()}>
|
<Dropdown.Item onClick={() => editor.chain().focus().setDocumentReference().run()}>
|
||||||
<IconDocument /> 文档
|
<IconDocument /> 文档
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
|
|
||||||
<Dropdown.Item onClick={() => editor.chain().focus().setDocumentChildren().run()}>
|
<Dropdown.Item onClick={() => editor.chain().focus().setDocumentChildren().run()}>
|
||||||
<IconDocument /> 子文档
|
<IconDocument /> 子文档
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { Select } from '@douyinfe/semi-ui';
|
import { Select } from '@douyinfe/semi-ui';
|
||||||
import { isTitleActive } from '../../services/isActive';
|
import { isTitleActive } from '../services/isActive';
|
||||||
|
|
||||||
const getCurrentCaretTitle = (editor) => {
|
const getCurrentCaretTitle = (editor) => {
|
||||||
if (editor.isActive('heading', { level: 1 })) return 1;
|
if (editor.isActive('heading', { level: 1 })) return 1;
|
|
@ -53,12 +53,15 @@ export const Search = ({ editor }) => {
|
||||||
<Button disabled={!results.length} onClick={() => editor.commands.replaceAll()}>
|
<Button disabled={!results.length} onClick={() => editor.commands.replaceAll()}>
|
||||||
全部替换
|
全部替换
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button disabled={!results.length} onClick={() => editor.commands.replace()}>
|
<Button disabled={!results.length} onClick={() => editor.commands.replace()}>
|
||||||
替换
|
替换
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button disabled={!results.length} onClick={() => editor.commands.goToPrevSearchResult()}>
|
<Button disabled={!results.length} onClick={() => editor.commands.goToPrevSearchResult()}>
|
||||||
上一个
|
上一个
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button disabled={!results.length} onClick={() => editor.commands.goToNextSearchResult()}>
|
<Button disabled={!results.length} onClick={() => editor.commands.goToNextSearchResult()}>
|
||||||
下一个
|
下一个
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -11,8 +11,8 @@ import {
|
||||||
IconDeleteTable,
|
IconDeleteTable,
|
||||||
} from 'components/icons';
|
} from 'components/icons';
|
||||||
import { Tooltip } from 'components/tooltip';
|
import { Tooltip } from 'components/tooltip';
|
||||||
import { Divider } from '../components/divider';
|
import { Divider } from '../wrappers/divider';
|
||||||
import { BubbleMenu } from './components/bubbleMenu';
|
import { BubbleMenu } from '../views/bubbleMenu';
|
||||||
import { Table } from '../extensions/table';
|
import { Table } from '../extensions/table';
|
||||||
|
|
||||||
export const TableBubbleMenu = ({ editor }) => {
|
export const TableBubbleMenu = ({ editor }) => {
|
||||||
|
|
|
@ -102,14 +102,14 @@ export function LowlightPlugin({
|
||||||
// (for example, a transaction that affects the entire document).
|
// (for example, a transaction that affects the entire document).
|
||||||
// Such transactions can happen during collab syncing via y-prosemirror, for example.
|
// Such transactions can happen during collab syncing via y-prosemirror, for example.
|
||||||
transaction.steps.some((step) => {
|
transaction.steps.some((step) => {
|
||||||
// @ts-ignore
|
|
||||||
return (
|
return (
|
||||||
|
// @ts-ignore
|
||||||
step.from !== undefined &&
|
step.from !== undefined &&
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
step.to !== undefined &&
|
step.to !== undefined &&
|
||||||
oldNodes.some((node) => {
|
oldNodes.some((node) => {
|
||||||
// @ts-ignore
|
|
||||||
return (
|
return (
|
||||||
|
// @ts-ignore
|
||||||
node.pos >= step.from &&
|
node.pos >= step.from &&
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
node.pos + node.node.nodeSize <= step.to
|
node.pos + node.node.nodeSize <= step.to
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
|
import cls from 'classnames';
|
||||||
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
|
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
|
||||||
import { Button, Typography, Spin, Collapsible, Space } from '@douyinfe/semi-ui';
|
import { Button, Typography, Spin, Collapsible, Space } from '@douyinfe/semi-ui';
|
||||||
import {
|
import {
|
||||||
|
@ -47,7 +48,7 @@ const getFileTypeIcon = (type: FileType) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AttachmentWrapper = ({ editor, node, updateAttributes }) => {
|
export const AttachmentWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
const $upload = useRef();
|
const $upload = useRef<HTMLInputElement>();
|
||||||
const isEditable = editor.isEditable;
|
const isEditable = editor.isEditable;
|
||||||
const { hasTrigger, fileName, fileSize, fileExt, fileType, url, error } = node.attrs;
|
const { hasTrigger, fileName, fileSize, fileExt, fileType, url, error } = node.attrs;
|
||||||
const [loading, toggleLoading] = useToggle(false);
|
const [loading, toggleLoading] = useToggle(false);
|
||||||
|
@ -55,7 +56,6 @@ export const AttachmentWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
|
|
||||||
const selectFile = () => {
|
const selectFile = () => {
|
||||||
if (!isEditable || error || url) return;
|
if (!isEditable || error || url) return;
|
||||||
// @ts-ignore
|
|
||||||
isEditable && $upload.current.click();
|
isEditable && $upload.current.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ export const AttachmentWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
const content = (() => {
|
const content = (() => {
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrap} onClick={selectFile}>
|
<div className={cls(styles.wrap, 'render-wrapper')} onClick={selectFile}>
|
||||||
<Text>{error}</Text>
|
<Text>{error}</Text>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -99,7 +99,7 @@ export const AttachmentWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
if (url) {
|
if (url) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.wrap} onClick={selectFile}>
|
<div className={cls(styles.wrap, 'render-wrapper')} onClick={selectFile}>
|
||||||
<Space>
|
<Space>
|
||||||
{getFileTypeIcon(type)}
|
{getFileTypeIcon(type)}
|
||||||
{fileName}.{fileExt}
|
{fileName}.{fileExt}
|
||||||
|
@ -139,7 +139,7 @@ export const AttachmentWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
|
|
||||||
if (isEditable && !url) {
|
if (isEditable && !url) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrap} onClick={selectFile}>
|
<div className={cls(styles.wrap, 'render-wrapper')} onClick={selectFile}>
|
||||||
<Spin spinning={loading}>
|
<Spin spinning={loading}>
|
||||||
<Text style={{ cursor: 'pointer' }}>{loading ? '正在上传中' : '请选择文件'}</Text>
|
<Text style={{ cursor: 'pointer' }}>{loading ? '正在上传中' : '请选择文件'}</Text>
|
||||||
<input ref={$upload} type="file" hidden onChange={handleFile} />
|
<input ref={$upload} type="file" hidden onChange={handleFile} />
|
|
@ -1,10 +1,11 @@
|
||||||
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
|
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
|
||||||
import { Banner as SemiBanner } from '@douyinfe/semi-ui';
|
import { Banner as SemiBanner } from '@douyinfe/semi-ui';
|
||||||
|
import cls from 'classnames';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
export const BannerWrapper = ({ node }) => {
|
export const BannerWrapper = ({ node }) => {
|
||||||
return (
|
return (
|
||||||
<NodeViewWrapper id="js-bannber-container" className={styles.wrap}>
|
<NodeViewWrapper id="js-bannber-container" className={cls(styles.wrap, 'render-wrapper')}>
|
||||||
<SemiBanner type={node.attrs.type} description={<NodeViewContent />} closeIcon={null} fullMode={false} />
|
<SemiBanner type={node.attrs.type} description={<NodeViewContent />} closeIcon={null} fullMode={false} />
|
||||||
</NodeViewWrapper>
|
</NodeViewWrapper>
|
||||||
);
|
);
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
|
import cls from 'classnames';
|
||||||
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
|
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
|
||||||
import { Button, Select, Tooltip } from '@douyinfe/semi-ui';
|
import { Button, Select, Tooltip } from '@douyinfe/semi-ui';
|
||||||
import { IconCopy } from '@douyinfe/semi-icons';
|
import { IconCopy } from '@douyinfe/semi-icons';
|
||||||
|
@ -17,7 +18,7 @@ export const CodeBlockWrapper = ({
|
||||||
const $container = useRef<HTMLPreElement>();
|
const $container = useRef<HTMLPreElement>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeViewWrapper className={styles.wrap}>
|
<NodeViewWrapper className={cls(styles.wrap, 'render-wrapper')}>
|
||||||
<div className={styles.handleWrap}>
|
<div className={styles.handleWrap}>
|
||||||
{isEditable && (
|
{isEditable && (
|
||||||
<Select
|
<Select
|
|
@ -1,14 +1,21 @@
|
||||||
.wrap {
|
.wrap {
|
||||||
margin: 28px 0 16px;
|
margin-top: 12px;
|
||||||
padding-top: 12px;
|
padding: 12px;
|
||||||
border-top: 1px solid var(--semi-color-border);
|
border: 1px solid var(--semi-color-border);
|
||||||
|
border-left: 0;
|
||||||
|
border-right: 0;
|
||||||
|
|
||||||
&.isEditable {
|
&.isEditable {
|
||||||
|
border: 0;
|
||||||
|
background-color: var(--semi-color-fill-0);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
outline: 1px solid var(--semi-color-link);
|
background-color: var(--semi-color-fill-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.itemWrap {
|
.itemWrap {
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--semi-color-text-1);
|
color: var(--semi-color-text-1);
|
||||||
border-color: var(--semi-color-border);
|
border-color: var(--semi-color-border);
|
||||||
|
@ -23,6 +30,8 @@
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--semi-color-text-1);
|
color: var(--semi-color-text-1);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
|
border: 1px solid var(--semi-color-border);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--semi-color-link);
|
color: var(--semi-color-link);
|
|
@ -34,7 +34,10 @@ export const DocumentChildrenWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
}, [node.attrs, wikiId, documentId]);
|
}, [node.attrs, wikiId, documentId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeViewWrapper as="div" className={cls(styles.wrap, isEditable && styles.isEditable)}>
|
<NodeViewWrapper
|
||||||
|
as="div"
|
||||||
|
className={cls('render-wrapper', styles.wrap, isEditable && styles.isEditable, 'documentChildren')}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<Text type="tertiary">子文档</Text>
|
<Text type="tertiary">子文档</Text>
|
|
@ -1,12 +1,17 @@
|
||||||
.wrap {
|
.wrap {
|
||||||
margin: 8px 0;
|
margin-top: 12px;
|
||||||
|
|
||||||
&.isEditable {
|
&.isEditable {
|
||||||
|
padding: 12px;
|
||||||
|
background-color: var(--semi-color-fill-0);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
outline: 1px solid var(--semi-color-link);
|
background-color: var(--semi-color-fill-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.itemWrap {
|
.itemWrap {
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--semi-color-text-1);
|
color: var(--semi-color-text-1);
|
||||||
border-color: var(--semi-color-border);
|
border-color: var(--semi-color-border);
|
|
@ -22,7 +22,7 @@ export const DocumentReferenceWrapper = ({ editor, node, updateAttributes }) =>
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeViewWrapper as="div" className={cls(styles.wrap, isEditable && styles.isEditable)}>
|
<NodeViewWrapper as="div" className={cls('render-wrapper', styles.wrap, isEditable && styles.isEditable)}>
|
||||||
<div>
|
<div>
|
||||||
{isEditable && (
|
{isEditable && (
|
||||||
<DataRender
|
<DataRender
|
|
@ -1,4 +1,5 @@
|
||||||
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
|
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
|
||||||
|
import cls from 'classnames';
|
||||||
import { Input } from '@douyinfe/semi-ui';
|
import { Input } from '@douyinfe/semi-ui';
|
||||||
import { Resizeable } from 'components/resizeable';
|
import { Resizeable } from 'components/resizeable';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
@ -7,13 +8,11 @@ export const IframeWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
const isEditable = editor.isEditable;
|
const isEditable = editor.isEditable;
|
||||||
const { url, width, height } = node.attrs;
|
const { url, width, height } = node.attrs;
|
||||||
|
|
||||||
console.log('render iframe', node.attrs);
|
|
||||||
|
|
||||||
const onResize = (size) => {
|
const onResize = (size) => {
|
||||||
updateAttributes({ width: size.width, height: size.height });
|
updateAttributes({ width: size.width, height: size.height });
|
||||||
};
|
};
|
||||||
const content = (
|
const content = (
|
||||||
<NodeViewContent as="div" className={styles.wrap}>
|
<NodeViewContent as="div" className={cls(styles.wrap, 'render-wrapper')}>
|
||||||
{isEditable && (
|
{isEditable && (
|
||||||
<div className={styles.handlerWrap}>
|
<div className={styles.handlerWrap}>
|
||||||
<Input placeholder={'输入外链地址'} value={url} onChange={(url) => updateAttributes({ url })}></Input>
|
<Input placeholder={'输入外链地址'} value={url} onChange={(url) => updateAttributes({ url })}></Input>
|
|
@ -1,6 +1,7 @@
|
||||||
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
|
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
|
||||||
import { Resizeable } from 'components/resizeable';
|
import { Resizeable } from 'components/resizeable';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
|
import cls from 'classnames';
|
||||||
import { Typography, Spin } from '@douyinfe/semi-ui';
|
import { Typography, Spin } from '@douyinfe/semi-ui';
|
||||||
import { useToggle } from 'hooks/useToggle';
|
import { useToggle } from 'hooks/useToggle';
|
||||||
import { uploadFile } from 'services/file';
|
import { uploadFile } from 'services/file';
|
||||||
|
@ -12,7 +13,7 @@ const { Text } = Typography;
|
||||||
export const ImageWrapper = ({ editor, node, updateAttributes }) => {
|
export const ImageWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
const isEditable = editor.isEditable;
|
const isEditable = editor.isEditable;
|
||||||
const { hasTrigger, error, src, alt, title, width, height, textAlign } = node.attrs;
|
const { hasTrigger, error, src, alt, title, width, height, textAlign } = node.attrs;
|
||||||
const $upload = useRef();
|
const $upload = useRef<HTMLInputElement>();
|
||||||
const [loading, toggleLoading] = useToggle(false);
|
const [loading, toggleLoading] = useToggle(false);
|
||||||
|
|
||||||
const onResize = (size) => {
|
const onResize = (size) => {
|
||||||
|
@ -21,7 +22,6 @@ export const ImageWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
|
|
||||||
const selectFile = () => {
|
const selectFile = () => {
|
||||||
if (!isEditable || error || src) return;
|
if (!isEditable || error || src) return;
|
||||||
// @ts-ignore
|
|
||||||
isEditable && $upload.current.click();
|
isEditable && $upload.current.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ export const ImageWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
const content = (() => {
|
const content = (() => {
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrap}>
|
<div className={cls(styles.wrap, 'render-wrapper')}>
|
||||||
<Text>{error}</Text>
|
<Text>{error}</Text>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -62,7 +62,7 @@ export const ImageWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
|
|
||||||
if (!src) {
|
if (!src) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrap} onClick={selectFile}>
|
<div className={cls(styles.wrap, 'render-wrapper')} onClick={selectFile}>
|
||||||
<Spin spinning={loading}>
|
<Spin spinning={loading}>
|
||||||
<Text style={{ cursor: 'pointer' }}>{loading ? '正在上传中' : '请选择图片'}</Text>
|
<Text style={{ cursor: 'pointer' }}>{loading ? '正在上传中' : '请选择图片'}</Text>
|
||||||
<input ref={$upload} accept="image/*" type="file" hidden onChange={handleFile} />
|
<input ref={$upload} accept="image/*" type="file" hidden onChange={handleFile} />
|
|
@ -1,5 +1,6 @@
|
||||||
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
|
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import cls from 'classnames';
|
||||||
import { Popover, TextArea, Typography, Space } from '@douyinfe/semi-ui';
|
import { Popover, TextArea, Typography, Space } from '@douyinfe/semi-ui';
|
||||||
import { IconHelpCircle } from '@douyinfe/semi-icons';
|
import { IconHelpCircle } from '@douyinfe/semi-icons';
|
||||||
import katex from 'katex';
|
import katex from 'katex';
|
||||||
|
@ -26,7 +27,7 @@ export const KatexWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeViewWrapper as="span" className={styles.wrap} contentEditable={false}>
|
<NodeViewWrapper as="span" className={cls(styles.wrap, 'render-wrapper')} contentEditable={false}>
|
||||||
{isEditable ? (
|
{isEditable ? (
|
||||||
<Popover
|
<Popover
|
||||||
showArrow
|
showArrow
|
|
@ -5,6 +5,8 @@
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: 1px solid var(--semi-color-border);
|
||||||
|
|
||||||
.jsmindWrap {
|
.jsmindWrap {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -29,8 +31,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: var(--border-radius);
|
|
||||||
border: 1px solid var(--semi-color-border);
|
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
> input {
|
> input {
|
|
@ -1,4 +1,5 @@
|
||||||
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
|
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
|
||||||
|
import cls from 'classnames';
|
||||||
import { useCallback, useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
import { Button } from '@douyinfe/semi-ui';
|
import { Button } from '@douyinfe/semi-ui';
|
||||||
import { IconMinus, IconPlus } from '@douyinfe/semi-icons';
|
import { IconMinus, IconPlus } from '@douyinfe/semi-icons';
|
||||||
|
@ -120,7 +121,7 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeViewWrapper className={styles.wrap}>
|
<NodeViewWrapper className={cls(styles.wrap, 'render-wrapper')}>
|
||||||
<NodeViewContent as="div">
|
<NodeViewContent as="div">
|
||||||
{isEditable ? (
|
{isEditable ? (
|
||||||
<Resizeable width={width} height={height} onChange={onResize}>
|
<Resizeable width={width} height={height} onChange={onResize}>
|
|
@ -1,6 +1,6 @@
|
||||||
.wrap {
|
.wrap {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin-right: 4px;
|
font-size: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
|
@ -1,14 +1,19 @@
|
||||||
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
|
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
|
||||||
import { Space, Popover, Tag, Input } from '@douyinfe/semi-ui';
|
import { Space, Popover, Tag, Input } from '@douyinfe/semi-ui';
|
||||||
|
import cls from 'classnames';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
export const StatusWrapper = ({ editor, node, updateAttributes }) => {
|
export const StatusWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
const isEditable = editor.isEditable;
|
const isEditable = editor.isEditable;
|
||||||
const { color, text } = node.attrs;
|
const { color, text } = node.attrs;
|
||||||
const content = <Tag color={color}>{text || '点击设置状态'}</Tag>;
|
const content = (
|
||||||
|
<Tag className="render-wrapper" color={color}>
|
||||||
|
{text || '点击设置状态'}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeViewWrapper as="span" className={styles.wrap}>
|
<NodeViewWrapper as="span" className={cls(styles.wrap, 'status')}>
|
||||||
{isEditable ? (
|
{isEditable ? (
|
||||||
<Popover
|
<Popover
|
||||||
showArrow
|
showArrow
|
||||||
|
@ -24,8 +29,7 @@ export const StatusWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
key={color}
|
key={color}
|
||||||
style={{ width: 24, height: 24, cursor: 'pointer' }}
|
style={{ width: 24, height: 24, cursor: 'pointer' }}
|
||||||
type="solid"
|
type="solid"
|
||||||
// @ts-ignore
|
color={color as unknown as any}
|
||||||
color={color}
|
|
||||||
onClick={() => updateAttributes({ color })}
|
onClick={() => updateAttributes({ color })}
|
||||||
></Tag>
|
></Tag>
|
||||||
);
|
);
|
|
@ -255,8 +255,7 @@
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: var(--border-radius);
|
margin: 0;
|
||||||
margin: 0.75rem 0px;
|
|
||||||
counter-reset: line 0;
|
counter-reset: line 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
min-width: 48px;
|
min-width: 48px;
|
||||||
|
@ -264,7 +263,6 @@
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
background-color: #0d0d0d;
|
background-color: #0d0d0d;
|
||||||
background-color: var(--semi-color-fill-0);
|
background-color: var(--semi-color-fill-0);
|
||||||
border: 1px solid var(--semi-color-border);
|
|
||||||
|
|
||||||
code {
|
code {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
@ -397,6 +395,82 @@
|
||||||
.search-result-current {
|
.search-result-current {
|
||||||
background: rgb(255, 0, 0);
|
background: rgb(255, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/******* 选中样式 *******/
|
||||||
|
hr.selected-node {
|
||||||
|
background-color: rgb(0, 101, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-status {
|
||||||
|
.semi-tag-default {
|
||||||
|
border: 1px solid var(--semi-color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected-node {
|
||||||
|
.semi-tag-default {
|
||||||
|
border: 1px solid rgb(0 101 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-codeBlock,
|
||||||
|
.node-documentChildren,
|
||||||
|
.node-documentReference,
|
||||||
|
.node-katex {
|
||||||
|
.render-wrapper {
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-katex {
|
||||||
|
.render-wrapper {
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-attachment,
|
||||||
|
.node-banner,
|
||||||
|
.node-iframe,
|
||||||
|
.node-image,
|
||||||
|
.node-katex,
|
||||||
|
.node-mind,
|
||||||
|
.node-codeBlock,
|
||||||
|
.node-documentChildren,
|
||||||
|
.node-documentReference {
|
||||||
|
&:not(.has-focus) {
|
||||||
|
::selection {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #e0ebfa
|
||||||
|
|
||||||
|
.render-wrapper {
|
||||||
|
position: relative;
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected-node {
|
||||||
|
.render-wrapper {
|
||||||
|
border: 1px solid rgb(0 101 255);
|
||||||
|
background-color: var(--semi-color-info-light-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableWrapper {
|
||||||
|
::selection {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected-node {
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
border-color: rgb(0 101 255);
|
||||||
|
background-color: var(--semi-color-info-light-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/******* 选中样式 *******/
|
||||||
}
|
}
|
||||||
|
|
||||||
.resize-cursor {
|
.resize-cursor {
|
||||||
|
|
Loading…
Reference in New Issue