mirror of https://github.com/fantasticit/think.git
Merge pull request #196 from fantasticit/fix/0916
commit
b03be31d3a
|
@ -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>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 }) => {
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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',
|
||||||
};
|
};
|
||||||
|
|
|
@ -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),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue