Merge pull request #196 from fantasticit/fix/0916

pull/198/head
fantasticit 2022-09-16 20:49:11 +08:00 committed by GitHub
commit b03be31d3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 89 additions and 67 deletions

View File

@ -21,6 +21,17 @@ export interface IProps {
onClosePreview?: () => void; onClosePreview?: () => void;
} }
const bodyStyle = {
overflow: 'auto',
};
const titleContainerStyle = {
marginBottom: 12,
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
} as React.CSSProperties;
const flexStyle = { display: 'flex' };
export const TemplateCard: React.FC<IProps> = ({ export const TemplateCard: React.FC<IProps> = ({
template, template,
onClick, onClick,
@ -35,26 +46,35 @@ export const TemplateCard: React.FC<IProps> = ({
Router.push(`/template/${template.id}/`); Router.push(`/template/${template.id}/`);
}, [template]); }, [template]);
const cancel = useCallback(() => {
toggleVisible(false);
onClosePreview && onClosePreview();
}, [toggleVisible, onClosePreview]);
const preview = useCallback(() => {
toggleVisible(true);
onOpenPreview && onOpenPreview();
}, [toggleVisible, onOpenPreview]);
const useTemplate = useCallback(() => {
onClick && onClick(template.id);
}, [onClick, template.id]);
return ( return (
<> <>
<Modal <Modal
title="模板预览" title="模板预览"
width={'calc(100vh - 120px)'} width={'calc(100vh - 120px)'}
height={'calc(100vh - 120px)'} height={'calc(100vh - 120px)'}
bodyStyle={{ bodyStyle={bodyStyle}
overflow: 'auto',
}}
visible={visible} visible={visible}
onCancel={() => { onCancel={cancel}
toggleVisible(false);
onClosePreview && onClosePreview();
}}
footer={null} footer={null}
fullScreen fullScreen
> >
<TemplateReader key={template.id} templateId={template.id} /> <TemplateReader key={template.id} templateId={template.id} />
</Modal> </Modal>
<div className={cls(styles.cardWrap, getClassNames(template.id))}> <div className={cls(styles.cardWrap, getClassNames(template.id))} onClick={useTemplate}>
<header> <header>
<IconDocument /> <IconDocument />
<div className={styles.rightWrap}> <div className={styles.rightWrap}>
@ -68,14 +88,7 @@ export const TemplateCard: React.FC<IProps> = ({
</div> </div>
</header> </header>
<main> <main>
<div <div style={titleContainerStyle}>
style={{
marginBottom: 12,
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
}}
>
<Text strong>{template.title}</Text> <Text strong>{template.title}</Text>
</div> </div>
<div> <div>
@ -92,28 +105,16 @@ export const TemplateCard: React.FC<IProps> = ({
</main> </main>
<footer> <footer>
<Text type="tertiary" size="small"> <Text type="tertiary" size="small">
<div style={{ display: 'flex' }}> <div style={flexStyle}>
使 使
{template.usageAmount} {template.usageAmount}
</div> </div>
</Text> </Text>
</footer> </footer>
<div className={styles.actions}> <div className={styles.actions}>
<Button <Button theme="solid" type="tertiary" onClick={preview}>
theme="solid"
type="tertiary"
onClick={() => {
toggleVisible(true);
onOpenPreview && onOpenPreview();
}}
>
</Button> </Button>
{onClick && (
<Button type="primary" theme="solid" onClick={() => onClick && onClick(template.id)}>
使
</Button>
)}
</div> </div>
</div> </div>
</> </>

View File

@ -164,7 +164,7 @@ define(function (require, exports, module) {
// 当前偏移加上历史偏移 // 当前偏移加上历史偏移
var offset = kity.Vector.fromPoints(lastPosition, currentPosition); var offset = kity.Vector.fromPoints(lastPosition, currentPosition);
dragger.move(offset); // dragger.move(offset);
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
e.originEvent.preventDefault(); e.originEvent.preventDefault();
@ -308,10 +308,10 @@ define(function (require, exports, module) {
dy = e.wheelDelta; dy = e.wheelDelta;
} }
this._viewDragger.move({ // this._viewDragger.move({
x: dx / 2.5, // x: dx / 2.5,
y: dy / 2.5, // y: dy / 2.5,
}); // });
var me = this; var me = this;
clearTimeout(this._mousewheeltimer); clearTimeout(this._mousewheeltimer);

View File

@ -1,4 +1,4 @@
import { Extension } from '@tiptap/core'; import { Editor as CoreEditor, Extension } from '@tiptap/core';
import { safeJSONParse } from 'helpers/json'; import { safeJSONParse } from 'helpers/json';
import { toggleMark } from 'prosemirror-commands'; import { toggleMark } from 'prosemirror-commands';
import { DOMParser, Fragment, Node, Schema } from 'prosemirror-model'; import { DOMParser, Fragment, Node, Schema } from 'prosemirror-model';
@ -53,7 +53,13 @@ interface IPasteOptions {
* *
* html prosemirror * html prosemirror
*/ */
htmlToProsemirror: (arg: { schema: Schema; html: string; needTitle: boolean; defaultTitle?: string }) => Node; htmlToProsemirror: (arg: {
editor: CoreEditor;
schema: Schema;
html: string;
needTitle: boolean;
defaultTitle?: string;
}) => Node;
/** /**
* markdown html * markdown html
@ -63,7 +69,7 @@ interface IPasteOptions {
/** /**
* markdown prosemirror * markdown prosemirror
*/ */
markdownToProsemirror: (arg: { schema: Schema; content: string; needTitle: boolean }) => Node; markdownToProsemirror: (arg: { editor: CoreEditor; schema: Schema; content: string; needTitle: boolean }) => Node;
/** /**
* prosemirror markdown * prosemirror markdown
@ -119,12 +125,6 @@ export const Paste = Extension.create<IPasteOptions>({
console.groupEnd(); console.groupEnd();
}); });
if (isInTitle(view.state)) {
if (text.length) {
return insertText(view, text);
}
}
// 直接复制节点 // 直接复制节点
if (node) { if (node) {
const json = safeJSONParse(node); const json = safeJSONParse(node);
@ -154,9 +154,10 @@ export const Paste = Extension.create<IPasteOptions>({
return true; return true;
} }
// FIXME:各家 office 套件标准不一样,是否需要做成用户自行选择粘贴 html 或者 图片? // TODO:各家 office 套件标准不一样,是否需要做成用户自行选择粘贴 html 或者 图片?
if (html?.includes('urn:schemas-microsoft-com:office') || html?.includes('</table>')) { if (html?.includes('urn:schemas-microsoft-com:office') || html?.includes('</table>')) {
const doc = htmlToProsemirror({ const doc = htmlToProsemirror({
editor,
schema: editor.schema, schema: editor.schema,
html, html,
needTitle: hasTitleExtension && !hasTitle, needTitle: hasTitleExtension && !hasTitle,
@ -212,7 +213,7 @@ export const Paste = Extension.create<IPasteOptions>({
event.preventDefault(); event.preventDefault();
const { tr } = view.state; const { tr } = view.state;
tr.replaceSelectionWith(view.state.schema.nodes.codeBlock.create({ language: pasteCodeLanguage })); tr.replaceSelectionWith(view.state.schema.nodes.codeBlock.create({ language: pasteCodeLanguage }));
tr.setSelection(TextSelection.near(tr.doc.resolve(Math.max(0, tr.selection.from - 2)))); tr.setSelection(TextSelection.near(tr.doc.resolve(Math.max(0, tr.selection.from - 1))));
tr.insertText(text.replace(/\r\n?/g, '\n')); tr.insertText(text.replace(/\r\n?/g, '\n'));
tr.setMeta('paste', true); tr.setMeta('paste', true);
view.dispatch(tr); view.dispatch(tr);
@ -220,10 +221,12 @@ export const Paste = Extension.create<IPasteOptions>({
} }
// 处理 markdown // 处理 markdown
if (markdownText || isMarkdown(text) || html.length === 0 || pasteCodeLanguage === 'markdown') { if (markdownText || isMarkdown(text)) {
console.log(text);
event.preventDefault(); event.preventDefault();
const schema = view.props.state.schema; const schema = view.props.state.schema;
const doc = markdownToProsemirror({ const doc = markdownToProsemirror({
editor,
schema, schema,
content: normalizeMarkdown(markdownText || text), content: normalizeMarkdown(markdownText || text),
needTitle: hasTitleExtension && !hasTitle, needTitle: hasTitleExtension && !hasTitle,
@ -239,8 +242,10 @@ export const Paste = Extension.create<IPasteOptions>({
return true; return true;
} }
if (text.length !== 0) { if (isInTitle(view.state)) {
return insertText(view, text); if (text.length) {
return insertText(view, text);
}
} }
return false; return false;

View File

@ -7,6 +7,7 @@ import { CodeBlock } from 'tiptap/core/extensions/code-block';
import { Countdown } from 'tiptap/core/extensions/countdown'; import { Countdown } from 'tiptap/core/extensions/countdown';
import { DocumentChildren } from 'tiptap/core/extensions/document-children'; import { DocumentChildren } from 'tiptap/core/extensions/document-children';
import { DocumentReference } from 'tiptap/core/extensions/document-reference'; import { DocumentReference } from 'tiptap/core/extensions/document-reference';
import { Excalidraw } from 'tiptap/core/extensions/excalidraw';
import { Flow } from 'tiptap/core/extensions/flow'; import { Flow } from 'tiptap/core/extensions/flow';
import { HorizontalRule } from 'tiptap/core/extensions/horizontal-rule'; import { HorizontalRule } from 'tiptap/core/extensions/horizontal-rule';
import { Iframe } from 'tiptap/core/extensions/iframe'; import { Iframe } from 'tiptap/core/extensions/iframe';
@ -47,6 +48,7 @@ const OTHER_BUBBLE_MENU_TYPES = [
Katex.name, Katex.name,
HorizontalRule.name, HorizontalRule.name,
Status.name, Status.name,
Excalidraw.name,
]; ];
export const Text = ({ editor }) => { export const Text = ({ editor }) => {

View File

@ -9,10 +9,10 @@ import { htmlToProsemirror as mdHTMLToProsemirror } from '../markdown-to-prosemi
* @param defaultTitle heading paragraph * @param defaultTitle heading paragraph
* @returns * @returns
*/ */
export const htmlToProsemirror = ({ schema, html, needTitle, defaultTitle = '' }) => { export const htmlToProsemirror = ({ editor, schema, html, needTitle, defaultTitle = '' }) => {
const parser = new DOMParser(); const parser = new DOMParser();
const { body } = parser.parseFromString(extractImage(html), 'text/html'); const { body } = parser.parseFromString(extractImage(html), 'text/html');
body.append(document.createComment(html)); body.append(document.createComment(html));
const doc = mdHTMLToProsemirror(body, needTitle, defaultTitle); const doc = mdHTMLToProsemirror(editor, body, needTitle, defaultTitle);
return doc; return doc;
}; };

View File

@ -1,7 +1,5 @@
import { Renderer } from './renderer'; import { Renderer } from './renderer';
const renderer = new Renderer();
/** /**
* *
* @param doc * @param doc
@ -56,7 +54,8 @@ function fixNode(doc) {
* @param defaultTitle heading paragraph * @param defaultTitle heading paragraph
* @returns * @returns
*/ */
export const htmlToProsemirror = (body, needTitle = false, defaultTitle = '') => { export const htmlToProsemirror = (editor, body, needTitle = false, defaultTitle = '') => {
let renderer = new Renderer(editor);
const json = renderer.render(body); const json = renderer.render(body);
// 设置标题 // 设置标题
@ -103,5 +102,7 @@ export const htmlToProsemirror = (body, needTitle = false, defaultTitle = '') =>
} }
fixNode(result); fixNode(result);
renderer = null;
return result; return result;
}; };

View File

@ -1,8 +1,12 @@
import { Editor } from '@tiptap/core';
export class Mark { export class Mark {
editor: Editor;
type: string; type: string;
DOMNode: HTMLElement; DOMNode: HTMLElement;
constructor(DomNode) { constructor(editor, DomNode) {
this.editor = editor;
this.type = 'mark'; this.type = 'mark';
this.DOMNode = DomNode; this.DOMNode = DomNode;
} }

View File

@ -1,8 +1,8 @@
import { Node } from './node'; import { Node } from './node';
export class ListItem extends Node { export class ListItem extends Node {
constructor(DomNode) { constructor(editor, DomNode) {
super(DomNode); super(editor, DomNode);
this.wrapper = { this.wrapper = {
type: 'paragraph', type: 'paragraph',
}; };

View File

@ -1,11 +1,15 @@
import { Editor } from '@tiptap/core';
import { getAttributes } from '../utils'; import { getAttributes } from '../utils';
export class Node { export class Node {
editor: Editor;
wrapper: unknown; wrapper: unknown;
type = 'node'; type = 'node';
DOMNode: HTMLElement; DOMNode: HTMLElement;
constructor(DomNode: HTMLElement) { constructor(editor, DomNode: HTMLElement) {
this.editor = editor;
this.wrapper = null; this.wrapper = null;
this.DOMNode = DomNode; this.DOMNode = DomNode;
} }
@ -17,7 +21,7 @@ export class Node {
data(): Record<string, unknown> { data(): Record<string, unknown> {
return { return {
type: this.type, type: this.type,
attrs: getAttributes(this.type, this.DOMNode), attrs: getAttributes(this.editor, this.type, this.DOMNode),
}; };
} }
} }

View File

@ -1,4 +1,5 @@
// 自定义节点 import { Editor } from '@tiptap/core';
// marks // marks
import { Bold } from './marks/bold'; import { Bold } from './marks/bold';
import { Code } from './marks/code'; import { Code } from './marks/code';
@ -45,12 +46,14 @@ import { Text } from './nodes/text';
import { Title } from './nodes/title'; import { Title } from './nodes/title';
export class Renderer { export class Renderer {
editor: Editor;
document: HTMLElement; document: HTMLElement;
nodes = []; nodes = [];
marks = []; marks = [];
storedMarks = []; storedMarks = [];
constructor() { constructor(editor) {
this.editor = editor;
this.document = undefined; this.document = undefined;
this.storedMarks = []; this.storedMarks = [];
@ -187,7 +190,7 @@ export class Renderer {
getMatchingClass(node, classes) { getMatchingClass(node, classes) {
for (const i in classes) { for (const i in classes) {
const Class = classes[i]; const Class = classes[i];
const instance = new Class(node); const instance = new Class(this.editor, node);
if (instance.matching()) { if (instance.matching()) {
return instance; return instance;
} }

View File

@ -1,4 +1,4 @@
import { AllExtensions } from 'tiptap/core/all-kit'; import { Editor } from '@tiptap/core';
/** /**
* tiptap extension DOM * tiptap extension DOM
@ -28,8 +28,10 @@ const getAttribute = (
}, ret); }, ret);
}; };
export const getAttributes = (name: string, element: HTMLElement): Record<string, unknown> => { export const getAttributes = (editor: Editor, name: string, element: HTMLElement): Record<string, unknown> => {
const ext = AllExtensions.find((ext) => ext && ext.name === name); if (!editor || !editor.extensionManager) return {};
const ext = Array.from(editor.extensionManager.extensions).find((ext) => ext && ext.name === name);
if (!ext) return {}; if (!ext) return {};

View File

@ -25,7 +25,7 @@ export const extractImage = (html) => {
}; };
// 将 markdown 字符串转换为 ProseMirror JSONDocument // 将 markdown 字符串转换为 ProseMirror JSONDocument
export const markdownToProsemirror = ({ schema, content, needTitle, defaultTitle = '' }) => { export const markdownToProsemirror = ({ editor, schema, content, needTitle, defaultTitle = '' }) => {
const html = markdownToHTML(content); const html = markdownToHTML(content);
if (!html) return null; if (!html) return null;
@ -33,7 +33,7 @@ export const markdownToProsemirror = ({ schema, content, needTitle, defaultTitle
const parser = new DOMParser(); const parser = new DOMParser();
const { body } = parser.parseFromString(extractImage(html), 'text/html'); const { body } = parser.parseFromString(extractImage(html), 'text/html');
body.append(document.createComment(content)); body.append(document.createComment(content));
const node = htmlToProsemirror(body, needTitle, defaultTitle); const node = htmlToProsemirror(editor, body, needTitle, defaultTitle);
return node; return node;
}; };