mirror of https://github.com/fantasticit/think.git
Merge pull request #152 from fantasticit/feat/export-docx
commit
213d726615
|
@ -62,6 +62,7 @@
|
|||
"docx": "^7.3.0",
|
||||
"dompurify": "^2.3.5",
|
||||
"downloadjs": "^1.4.7",
|
||||
"html-to-docx": "^1.4.0",
|
||||
"interactjs": "^1.10.11",
|
||||
"katex": "^0.15.2",
|
||||
"kity": "^2.0.4",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Badge, Button, Dropdown, Modal, Space, Typography } from '@douyinfe/semi-ui';
|
||||
import { IDocument } from '@think/domains';
|
||||
import { IconJSON, IconMarkdown, IconPDF, IconWord } from 'components/icons';
|
||||
import { useDocumentDetail } from 'data/document';
|
||||
import download from 'downloadjs';
|
||||
import { safeJSONParse, safeJSONStringify } from 'helpers/json';
|
||||
import { IsOnMobile } from 'hooks/use-on-mobile';
|
||||
|
@ -8,7 +9,6 @@ import { useToggle } from 'hooks/use-toggle';
|
|||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { createEditor } from 'tiptap/core';
|
||||
import { AllExtensions } from 'tiptap/core/all-kit';
|
||||
import { prosemirrorToDocx } from 'tiptap/docx';
|
||||
import { prosemirrorToMarkdown } from 'tiptap/markdown/prosemirror-to-markdown';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
@ -24,6 +24,7 @@ interface IProps {
|
|||
export const DocumentExporter: React.FC<IProps> = ({ document, render }) => {
|
||||
const { isMobile } = IsOnMobile.useHook();
|
||||
const [visible, toggleVisible] = useToggle(false);
|
||||
const { exportDocx } = useDocumentDetail(document.id);
|
||||
|
||||
const editor = useMemo(() => {
|
||||
return createEditor({
|
||||
|
@ -47,10 +48,13 @@ export const DocumentExporter: React.FC<IProps> = ({ document, render }) => {
|
|||
}, [document, editor]);
|
||||
|
||||
const exportWord = useCallback(() => {
|
||||
prosemirrorToDocx(editor.view, editor.state).then((buffer) => {
|
||||
download(buffer, `${document.title}.docx`);
|
||||
});
|
||||
}, [document, editor]);
|
||||
const editorContent = editor.view.dom.closest('.ProseMirror');
|
||||
if (editorContent) {
|
||||
exportDocx(editorContent.outerHTML).then((res) => {
|
||||
download(Buffer.from(res as Buffer), `${document.title}.docx`);
|
||||
});
|
||||
}
|
||||
}, [editor, exportDocx, document]);
|
||||
|
||||
const exportPDF = useCallback(() => {
|
||||
printEditorContent(editor.view);
|
||||
|
|
|
@ -199,7 +199,19 @@ export const useDocumentDetail = (documentId, options: UseQueryOptions<IDocument
|
|||
[refetch, documentId]
|
||||
);
|
||||
|
||||
return { data, loading: isLoading, error, update, toggleStatus };
|
||||
/**
|
||||
* 导出文档
|
||||
*/
|
||||
const exportDocx = useCallback(async (content) => {
|
||||
const res = await HttpClient.request({
|
||||
method: DocumentApiDefinition.exportDocx.method,
|
||||
url: DocumentApiDefinition.exportDocx.client(),
|
||||
data: { content },
|
||||
});
|
||||
return res;
|
||||
}, []);
|
||||
|
||||
return { data, loading: isLoading, error, update, toggleStatus, exportDocx };
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -47,13 +47,13 @@ export const Title = Node.create<TitleOptions>({
|
|||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: 'div[class=node-title]',
|
||||
tag: 'h1[class=node-title]',
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
|
||||
return ['h1', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
|
|
|
@ -1,149 +0,0 @@
|
|||
import axios from 'axios';
|
||||
import { HeadingLevel } from 'docx';
|
||||
import { EditorState } from 'prosemirror-state';
|
||||
import { EditorView } from 'prosemirror-view';
|
||||
import { Attachment } from 'tiptap/core/extensions/attachment';
|
||||
import { BulletList } from 'tiptap/core/extensions/bullet-list';
|
||||
import { Callout } from 'tiptap/core/extensions/callout';
|
||||
import { CodeBlock } from 'tiptap/core/extensions/code-block';
|
||||
import { DocumentChildren } from 'tiptap/core/extensions/document-children';
|
||||
import { DocumentReference } from 'tiptap/core/extensions/document-reference';
|
||||
import { Flow } from 'tiptap/core/extensions/flow';
|
||||
import { HardBreak } from 'tiptap/core/extensions/hard-break';
|
||||
import { HorizontalRule } from 'tiptap/core/extensions/horizontal-rule';
|
||||
import { Iframe } from 'tiptap/core/extensions/iframe';
|
||||
import { Katex } from 'tiptap/core/extensions/katex';
|
||||
import { ListItem } from 'tiptap/core/extensions/listItem';
|
||||
import { Mind } from 'tiptap/core/extensions/mind';
|
||||
import { OrderedList } from 'tiptap/core/extensions/ordered-list';
|
||||
import { Status } from 'tiptap/core/extensions/status';
|
||||
import { TableOfContents } from 'tiptap/core/extensions/table-of-contents';
|
||||
import { TaskItem } from 'tiptap/core/extensions/task-item';
|
||||
import { TaskList } from 'tiptap/core/extensions/task-list';
|
||||
import { Title } from 'tiptap/core/extensions/title';
|
||||
|
||||
import { defaultMarks, defaultNodes, DocxSerializer, writeDocx } from './prosemirror-docx';
|
||||
|
||||
function getLatexFromNode(node): string {
|
||||
return node.attrs.text;
|
||||
}
|
||||
|
||||
const nodeSerializer = {
|
||||
...defaultNodes,
|
||||
[Title.name](state, node) {
|
||||
state.renderInline(node);
|
||||
state.closeBlock(node, { heading: HeadingLevel.TITLE });
|
||||
},
|
||||
[DocumentChildren.name](state, node) {
|
||||
state.renderInline(node);
|
||||
state.closeBlock(node);
|
||||
},
|
||||
[DocumentReference.name](state, node) {
|
||||
state.renderInline(node);
|
||||
state.closeBlock(node);
|
||||
},
|
||||
[TableOfContents.name](state, node) {
|
||||
state.renderInline(node);
|
||||
state.closeBlock(node);
|
||||
},
|
||||
[BulletList.name](state, node) {
|
||||
state.renderList(node, 'bullets');
|
||||
},
|
||||
[OrderedList.name](state, node) {
|
||||
state.renderList(node, 'numbered');
|
||||
},
|
||||
[ListItem.name](state, node) {
|
||||
state.renderListItem(node);
|
||||
},
|
||||
[HorizontalRule.name](state, node) {
|
||||
state.closeBlock(node, { thematicBreak: true });
|
||||
state.closeBlock(node);
|
||||
},
|
||||
[TaskList.name](state, node) {
|
||||
state.renderInline(node);
|
||||
state.closeBlock(node);
|
||||
},
|
||||
[TaskItem.name](state, node) {
|
||||
state.renderInline(node);
|
||||
state.closeBlock(node);
|
||||
},
|
||||
[CodeBlock.name](state, node) {
|
||||
state.renderContent(node);
|
||||
state.closeBlock(node);
|
||||
},
|
||||
[Status.name](state, node) {
|
||||
state.text(node.attrs.text ?? '');
|
||||
state.closeBlock(node);
|
||||
},
|
||||
[Flow.name](state, node) {
|
||||
state.renderContent(node);
|
||||
state.closeBlock(node);
|
||||
},
|
||||
[Mind.name](state, node) {
|
||||
state.renderContent(node);
|
||||
state.closeBlock(node);
|
||||
},
|
||||
[HardBreak.name](state, node) {
|
||||
state.addRunOptions({ break: 1 });
|
||||
},
|
||||
[Katex.name](state, node) {
|
||||
state.math(getLatexFromNode(node), { inline: false });
|
||||
state.closeBlock(node);
|
||||
},
|
||||
[Iframe.name](state, node) {
|
||||
state.renderContent(node);
|
||||
state.closeBlock(node);
|
||||
},
|
||||
[Attachment.name](state, node) {
|
||||
state.renderContent(node);
|
||||
state.closeBlock(node);
|
||||
},
|
||||
[Callout.name](state, node) {
|
||||
state.renderContent(node);
|
||||
state.closeBlock(node);
|
||||
},
|
||||
};
|
||||
|
||||
const docxSerializer = new DocxSerializer(nodeSerializer, defaultMarks);
|
||||
|
||||
async function getImageBuffer(src: string) {
|
||||
const image = await axios
|
||||
.get(src, {
|
||||
responseType: 'arraybuffer',
|
||||
})
|
||||
.catch(() => {
|
||||
return { data: '' };
|
||||
});
|
||||
return Buffer.from(image.data);
|
||||
}
|
||||
|
||||
export const prosemirrorToDocx = async (view: EditorView, state: EditorState): Promise<Blob> => {
|
||||
const dom = view.dom.closest('.ProseMirror');
|
||||
const imageBufferCache = new Map();
|
||||
const images = Array.from(await dom.querySelectorAll('img')) as HTMLImageElement[];
|
||||
|
||||
await Promise.all(
|
||||
images.map(async (img) => {
|
||||
try {
|
||||
const buffer = await getImageBuffer(img.src);
|
||||
imageBufferCache.set(img.src, buffer);
|
||||
} catch (e) {
|
||||
imageBufferCache.set(img.src, Buffer.from('图片加载失败'));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const wordDocument = docxSerializer.serialize(state.doc, {
|
||||
getImageBuffer(src) {
|
||||
return imageBufferCache.get(src);
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async (resolve) => {
|
||||
await writeDocx(wordDocument, (buffer) => {
|
||||
imageBufferCache.clear();
|
||||
resolve(new Blob([buffer]));
|
||||
});
|
||||
});
|
||||
};
|
|
@ -1,4 +0,0 @@
|
|||
export { defaultDocxSerializer, defaultMarks, defaultNodes } from './schema';
|
||||
export type { MarkSerializer, NodeSerializer } from './serializer';
|
||||
export { DocxSerializer, DocxSerializerState } from './serializer';
|
||||
export { createDocFromState, writeDocx } from './utils';
|
|
@ -1,48 +0,0 @@
|
|||
import { AlignmentType, convertInchesToTwip, ILevelsOptions, LevelFormat } from 'docx';
|
||||
|
||||
import { INumbering } from './types';
|
||||
|
||||
function basicIndentStyle(indent: number): Pick<ILevelsOptions, 'style' | 'alignment'> {
|
||||
return {
|
||||
alignment: AlignmentType.START,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: convertInchesToTwip(indent), hanging: convertInchesToTwip(0.18) },
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const numbered = Array(3)
|
||||
.fill([LevelFormat.DECIMAL, LevelFormat.LOWER_LETTER, LevelFormat.LOWER_ROMAN])
|
||||
.flat()
|
||||
.map((format, level) => ({
|
||||
level,
|
||||
format,
|
||||
text: `%${level + 1}.`,
|
||||
...basicIndentStyle((level + 1) / 2),
|
||||
}));
|
||||
|
||||
const bullets = Array(3)
|
||||
.fill(['●', '○', '■'])
|
||||
.flat()
|
||||
.map((text, level) => ({
|
||||
level,
|
||||
format: LevelFormat.BULLET,
|
||||
text,
|
||||
...basicIndentStyle((level + 1) / 2),
|
||||
}));
|
||||
|
||||
const styles = {
|
||||
numbered,
|
||||
bullets,
|
||||
};
|
||||
|
||||
export type NumberingStyles = keyof typeof styles;
|
||||
|
||||
export function createNumbering(reference: string, style: NumberingStyles): INumbering {
|
||||
return {
|
||||
reference,
|
||||
levels: styles[style],
|
||||
};
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
import { HeadingLevel, ShadingType } from 'docx';
|
||||
|
||||
import { DocxSerializer, MarkSerializer, NodeSerializer } from './serializer';
|
||||
import { getLatexFromNode } from './utils';
|
||||
|
||||
export const defaultNodes: NodeSerializer = {
|
||||
text(state, node) {
|
||||
state.text(node.text ?? '');
|
||||
},
|
||||
paragraph(state, node) {
|
||||
state.renderInline(node);
|
||||
state.closeBlock(node);
|
||||
},
|
||||
heading(state, node) {
|
||||
state.renderInline(node);
|
||||
const heading = [
|
||||
HeadingLevel.HEADING_1,
|
||||
HeadingLevel.HEADING_2,
|
||||
HeadingLevel.HEADING_3,
|
||||
HeadingLevel.HEADING_4,
|
||||
HeadingLevel.HEADING_5,
|
||||
HeadingLevel.HEADING_6,
|
||||
][node.attrs.level - 1];
|
||||
state.closeBlock(node, { heading });
|
||||
},
|
||||
blockquote(state, node) {
|
||||
state.renderContent(node, { style: 'IntenseQuote' });
|
||||
},
|
||||
code_block(state, node) {
|
||||
// TODO: something for code
|
||||
state.renderContent(node);
|
||||
state.closeBlock(node);
|
||||
},
|
||||
horizontal_rule(state, node) {
|
||||
// Kinda hacky, but this works to insert two paragraphs, the first with a break
|
||||
state.closeBlock(node, { thematicBreak: true });
|
||||
state.closeBlock(node);
|
||||
},
|
||||
hard_break(state) {
|
||||
state.addRunOptions({ break: 1 });
|
||||
},
|
||||
ordered_list(state, node) {
|
||||
state.renderList(node, 'numbered');
|
||||
},
|
||||
bullet_list(state, node) {
|
||||
state.renderList(node, 'bullets');
|
||||
},
|
||||
list_item(state, node) {
|
||||
state.renderListItem(node);
|
||||
},
|
||||
// Presentational
|
||||
image(state, node) {
|
||||
const { src } = node.attrs;
|
||||
state.image(src);
|
||||
state.closeBlock(node);
|
||||
},
|
||||
// Technical
|
||||
math(state, node) {
|
||||
state.math(getLatexFromNode(node), { inline: true });
|
||||
},
|
||||
equation(state, node) {
|
||||
const { id, numbered } = node.attrs;
|
||||
state.math(getLatexFromNode(node), { inline: false, numbered, id });
|
||||
state.closeBlock(node);
|
||||
},
|
||||
table(state, node) {
|
||||
state.table(node);
|
||||
},
|
||||
};
|
||||
|
||||
export const defaultMarks: MarkSerializer = {
|
||||
em() {
|
||||
return { italics: true };
|
||||
},
|
||||
strong() {
|
||||
return { bold: true };
|
||||
},
|
||||
link() {
|
||||
// Note, this is handled specifically in the serializer
|
||||
// Word treats links more like a Node rather than a mark
|
||||
return {};
|
||||
},
|
||||
code() {
|
||||
return {
|
||||
font: {
|
||||
name: 'Monospace',
|
||||
},
|
||||
color: '000000',
|
||||
shading: {
|
||||
type: ShadingType.SOLID,
|
||||
color: 'D2D3D2',
|
||||
fill: 'D2D3D2',
|
||||
},
|
||||
};
|
||||
},
|
||||
abbr() {
|
||||
// TODO: abbreviation
|
||||
return {};
|
||||
},
|
||||
subscript() {
|
||||
return { subScript: true };
|
||||
},
|
||||
superscript() {
|
||||
return { superScript: true };
|
||||
},
|
||||
strikethrough() {
|
||||
// doubleStrike!
|
||||
return { strike: true };
|
||||
},
|
||||
underline() {
|
||||
return {
|
||||
underline: {},
|
||||
};
|
||||
},
|
||||
smallcaps() {
|
||||
return { smallCaps: true };
|
||||
},
|
||||
allcaps() {
|
||||
return { allCaps: true };
|
||||
},
|
||||
};
|
||||
|
||||
export const defaultDocxSerializer = new DocxSerializer(defaultNodes, defaultMarks);
|
|
@ -1,376 +0,0 @@
|
|||
import sizeOf from 'buffer-image-size';
|
||||
import {
|
||||
AlignmentType,
|
||||
Bookmark,
|
||||
ExternalHyperlink,
|
||||
FootnoteReferenceRun,
|
||||
ImageRun,
|
||||
InternalHyperlink,
|
||||
IParagraphOptions,
|
||||
IRunOptions,
|
||||
ITableCellOptions,
|
||||
Math,
|
||||
MathRun,
|
||||
Paragraph,
|
||||
ParagraphChild,
|
||||
SequentialIdentifier,
|
||||
SimpleField,
|
||||
Table,
|
||||
TableCell,
|
||||
TableRow,
|
||||
TabStopPosition,
|
||||
TabStopType,
|
||||
TextRun,
|
||||
WidthType,
|
||||
} from 'docx';
|
||||
import { Mark, Node as ProsemirrorNode, Schema } from 'prosemirror-model';
|
||||
|
||||
import { createNumbering, NumberingStyles } from './numbering';
|
||||
import { IFootnotes, INumbering, Mutable } from './types';
|
||||
import { createDocFromState, createShortId } from './utils';
|
||||
|
||||
// This is duplicated from @curvenote/schema
|
||||
export type AlignOptions = 'left' | 'center' | 'right';
|
||||
|
||||
export type NodeSerializer<S extends Schema = any> = Record<
|
||||
string,
|
||||
(state: DocxSerializerState<S>, node: ProsemirrorNode<S>, parent: ProsemirrorNode<S>, index: number) => void
|
||||
>;
|
||||
|
||||
export type MarkSerializer<S extends Schema = any> = Record<
|
||||
string,
|
||||
(state: DocxSerializerState<S>, node: ProsemirrorNode<S>, mark: Mark<S>) => IRunOptions
|
||||
>;
|
||||
|
||||
export type Options = {
|
||||
getImageBuffer: (src: string) => Buffer;
|
||||
};
|
||||
|
||||
export type IMathOpts = {
|
||||
inline?: boolean;
|
||||
id?: string | null;
|
||||
numbered?: boolean;
|
||||
};
|
||||
|
||||
const MAX_IMAGE_WIDTH = 600;
|
||||
|
||||
function createReferenceBookmark(id: string, kind: 'Equation' | 'Figure' | 'Table', before?: string, after?: string) {
|
||||
const textBefore = before ? [new TextRun(before)] : [];
|
||||
const textAfter = after ? [new TextRun(after)] : [];
|
||||
return new Bookmark({
|
||||
id,
|
||||
children: [...textBefore, new SequentialIdentifier(kind), ...textAfter],
|
||||
});
|
||||
}
|
||||
|
||||
export class DocxSerializerState<S extends Schema = any> {
|
||||
nodes: NodeSerializer<S>;
|
||||
|
||||
options: Options;
|
||||
|
||||
marks: MarkSerializer<S>;
|
||||
|
||||
children: (Paragraph | Table)[];
|
||||
|
||||
numbering: INumbering[];
|
||||
|
||||
footnotes: IFootnotes = {};
|
||||
|
||||
nextRunOpts?: IRunOptions;
|
||||
|
||||
current: ParagraphChild[] = [];
|
||||
|
||||
currentLink?: { link: string; children: IRunOptions[] };
|
||||
|
||||
// Optionally add options
|
||||
nextParentParagraphOpts?: IParagraphOptions;
|
||||
|
||||
currentNumbering?: { reference: string; level: number };
|
||||
|
||||
constructor(nodes: NodeSerializer<S>, marks: MarkSerializer<S>, options: Options) {
|
||||
this.nodes = nodes;
|
||||
this.marks = marks;
|
||||
// @ts-ignore
|
||||
this.options = options ?? {};
|
||||
this.children = [];
|
||||
this.numbering = [];
|
||||
}
|
||||
|
||||
renderContent(parent: ProsemirrorNode<S>, opts?: IParagraphOptions) {
|
||||
parent.forEach((node, _, i) => {
|
||||
if (opts) this.addParagraphOptions(opts);
|
||||
this.render(node, parent, i);
|
||||
});
|
||||
}
|
||||
|
||||
render(node: ProsemirrorNode<S>, parent: ProsemirrorNode<S>, index: number) {
|
||||
if (typeof parent === 'number') throw new Error('!');
|
||||
if (!this.nodes[node.type.name]) throw new Error(`Token type \`${node.type.name}\` not supported by Word renderer`);
|
||||
this.nodes[node.type.name](this, node, parent, index);
|
||||
}
|
||||
|
||||
renderMarks(node: ProsemirrorNode<S>, marks: Mark[]): IRunOptions {
|
||||
return marks
|
||||
.map((mark) => {
|
||||
return this.marks[mark.type.name]?.(this, node, mark);
|
||||
})
|
||||
.reduce((a, b) => ({ ...a, ...b }), {});
|
||||
}
|
||||
|
||||
renderInline(parent: ProsemirrorNode<S>) {
|
||||
// Pop the stack over to this object when we encounter a link, and closeLink restores it
|
||||
let currentLink: { link: string; stack: ParagraphChild[] } | undefined;
|
||||
const closeLink = () => {
|
||||
if (!currentLink) return;
|
||||
const hyperlink = new ExternalHyperlink({
|
||||
link: currentLink.link,
|
||||
// child: this.current[0],
|
||||
children: this.current,
|
||||
});
|
||||
this.current = [...currentLink.stack, hyperlink];
|
||||
currentLink = undefined;
|
||||
};
|
||||
const openLink = (href: string) => {
|
||||
const sameLink = href === currentLink?.link;
|
||||
this.addRunOptions({ style: 'Hyperlink' });
|
||||
// TODO: https://github.com/dolanmiu/docx/issues/1119
|
||||
// Remove the if statement here and oneLink!
|
||||
const oneLink = true;
|
||||
if (!oneLink) {
|
||||
closeLink();
|
||||
} else {
|
||||
if (currentLink && sameLink) return;
|
||||
if (currentLink && !sameLink) {
|
||||
// Close previous, and open a new one
|
||||
closeLink();
|
||||
}
|
||||
}
|
||||
currentLink = {
|
||||
link: href,
|
||||
stack: this.current,
|
||||
};
|
||||
this.current = [];
|
||||
};
|
||||
const progress = (node: ProsemirrorNode<S>, offset: number, index: number) => {
|
||||
const links = node.marks.filter((m) => m.type.name === 'link');
|
||||
const hasLink = links.length > 0;
|
||||
if (hasLink) {
|
||||
openLink(links[0].attrs.href);
|
||||
} else if (!hasLink && currentLink) {
|
||||
closeLink();
|
||||
}
|
||||
if (node.isText) {
|
||||
this.text(node.text, this.renderMarks(node, node.marks));
|
||||
} else {
|
||||
this.render(node, parent, index);
|
||||
}
|
||||
};
|
||||
parent.forEach(progress);
|
||||
// Must call close at the end of everything, just in case
|
||||
closeLink();
|
||||
}
|
||||
|
||||
renderList(node: ProsemirrorNode<S>, style: NumberingStyles) {
|
||||
if (!this.currentNumbering) {
|
||||
const nextId = createShortId();
|
||||
this.numbering.push(createNumbering(nextId, style));
|
||||
this.currentNumbering = { reference: nextId, level: 0 };
|
||||
} else {
|
||||
const { reference, level } = this.currentNumbering;
|
||||
this.currentNumbering = { reference, level: level + 1 };
|
||||
}
|
||||
this.renderContent(node);
|
||||
if (this.currentNumbering.level === 0) {
|
||||
delete this.currentNumbering;
|
||||
} else {
|
||||
const { reference, level } = this.currentNumbering;
|
||||
this.currentNumbering = { reference, level: level - 1 };
|
||||
}
|
||||
}
|
||||
|
||||
// This is a pass through to the paragraphs, etc. underneath they will close the block
|
||||
renderListItem(node: ProsemirrorNode<S>) {
|
||||
if (!this.currentNumbering) throw new Error('Trying to create a list item without a list?');
|
||||
this.addParagraphOptions({ numbering: this.currentNumbering });
|
||||
this.renderContent(node);
|
||||
}
|
||||
|
||||
addParagraphOptions(opts: IParagraphOptions) {
|
||||
this.nextParentParagraphOpts = { ...this.nextParentParagraphOpts, ...opts };
|
||||
}
|
||||
|
||||
addRunOptions(opts: IRunOptions) {
|
||||
this.nextRunOpts = { ...this.nextRunOpts, ...opts };
|
||||
}
|
||||
|
||||
text(text: string | null | undefined, opts?: IRunOptions) {
|
||||
if (!text) return;
|
||||
this.current.push(new TextRun({ text, ...this.nextRunOpts, ...opts }));
|
||||
delete this.nextRunOpts;
|
||||
}
|
||||
|
||||
math(latex: string, opts: IMathOpts = { inline: true }) {
|
||||
if (opts.inline || !opts.numbered) {
|
||||
this.current.push(new Math({ children: [new MathRun(latex)] }));
|
||||
return;
|
||||
}
|
||||
const id = opts.id ?? createShortId();
|
||||
this.current = [
|
||||
new TextRun('\t'),
|
||||
new Math({
|
||||
children: [new MathRun(latex)],
|
||||
}),
|
||||
new TextRun('\t('),
|
||||
createReferenceBookmark(id, 'Equation'),
|
||||
new TextRun(')'),
|
||||
];
|
||||
this.addParagraphOptions({
|
||||
tabStops: [
|
||||
{
|
||||
type: TabStopType.CENTER,
|
||||
position: TabStopPosition.MAX / 2,
|
||||
},
|
||||
{
|
||||
type: TabStopType.RIGHT,
|
||||
position: TabStopPosition.MAX,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// not sure what this actually is, seems to be close for 8.5x11
|
||||
maxImageWidth = MAX_IMAGE_WIDTH;
|
||||
|
||||
image(src: string, widthPercent = 70, align: AlignOptions = 'center') {
|
||||
const buffer = this.options.getImageBuffer(src);
|
||||
|
||||
if (!buffer) return;
|
||||
|
||||
const dimensions = sizeOf(buffer);
|
||||
const aspect = dimensions.height / dimensions.width;
|
||||
const width = this.maxImageWidth * (widthPercent / 100);
|
||||
this.current.push(
|
||||
new ImageRun({
|
||||
data: buffer,
|
||||
transformation: {
|
||||
width,
|
||||
height: width * aspect,
|
||||
},
|
||||
})
|
||||
);
|
||||
let alignment: AlignmentType;
|
||||
switch (align) {
|
||||
case 'right':
|
||||
alignment = AlignmentType.RIGHT;
|
||||
break;
|
||||
case 'left':
|
||||
alignment = AlignmentType.LEFT;
|
||||
break;
|
||||
default:
|
||||
alignment = AlignmentType.CENTER;
|
||||
}
|
||||
this.addParagraphOptions({
|
||||
alignment,
|
||||
});
|
||||
}
|
||||
|
||||
table(node: ProsemirrorNode<S>) {
|
||||
const actualChildren = this.children;
|
||||
const rows: TableRow[] = [];
|
||||
node.content.forEach(({ content: rowContent }) => {
|
||||
const cells: TableCell[] = [];
|
||||
// Check if all cells are headers in this row
|
||||
let tableHeader = true;
|
||||
rowContent.forEach((cell) => {
|
||||
if (cell.type.name !== 'table_header') {
|
||||
tableHeader = false;
|
||||
}
|
||||
});
|
||||
// This scales images inside of tables
|
||||
this.maxImageWidth = MAX_IMAGE_WIDTH / rowContent.childCount;
|
||||
rowContent.forEach((cell) => {
|
||||
this.children = [];
|
||||
this.renderContent(cell);
|
||||
const tableCellOpts: Mutable<ITableCellOptions> = {
|
||||
children: this.children,
|
||||
};
|
||||
const colspan = cell.attrs.colspan ?? 1;
|
||||
const rowspan = cell.attrs.rowspan ?? 1;
|
||||
if (colspan > 1) tableCellOpts.columnSpan = colspan;
|
||||
if (rowspan > 1) tableCellOpts.rowSpan = rowspan;
|
||||
cells.push(new TableCell(tableCellOpts));
|
||||
});
|
||||
rows.push(new TableRow({ children: cells, tableHeader }));
|
||||
});
|
||||
this.maxImageWidth = MAX_IMAGE_WIDTH;
|
||||
|
||||
const table = new Table({
|
||||
rows,
|
||||
// columnWidths: Array.from({ length: rows[0].cells.length }, () => 3505),
|
||||
});
|
||||
actualChildren.push(table);
|
||||
// If there are multiple tables, this seperates them
|
||||
actualChildren.push(new Paragraph(''));
|
||||
this.children = actualChildren;
|
||||
}
|
||||
|
||||
captionLabel(id: string, kind: 'Figure' | 'Table', { suffix } = { suffix: ': ' }) {
|
||||
this.current.push(...[createReferenceBookmark(id, kind, `${kind} `), new TextRun(suffix)]);
|
||||
}
|
||||
|
||||
$footnoteCounter = 0;
|
||||
|
||||
footnote(node: ProsemirrorNode<S>) {
|
||||
const { current, nextRunOpts } = this;
|
||||
// Delete everything and work with the footnote inline on the current
|
||||
this.current = [];
|
||||
delete this.nextRunOpts;
|
||||
|
||||
this.$footnoteCounter += 1;
|
||||
this.renderInline(node);
|
||||
this.footnotes[this.$footnoteCounter] = {
|
||||
children: [new Paragraph({ children: this.current })],
|
||||
};
|
||||
this.current = current;
|
||||
this.nextRunOpts = nextRunOpts;
|
||||
this.current.push(new FootnoteReferenceRun(this.$footnoteCounter));
|
||||
}
|
||||
|
||||
closeBlock(node: ProsemirrorNode<S>, props?: IParagraphOptions) {
|
||||
const paragraph = new Paragraph({
|
||||
children: this.current,
|
||||
...this.nextParentParagraphOpts,
|
||||
...props,
|
||||
});
|
||||
this.current = [];
|
||||
delete this.nextParentParagraphOpts;
|
||||
this.children.push(paragraph);
|
||||
}
|
||||
|
||||
createReference(id: string, before?: string, after?: string) {
|
||||
const children: ParagraphChild[] = [];
|
||||
if (before) children.push(new TextRun(before));
|
||||
children.push(new SimpleField(`REF ${id} \\h`));
|
||||
if (after) children.push(new TextRun(after));
|
||||
const ref = new InternalHyperlink({ anchor: id, children });
|
||||
this.current.push(ref);
|
||||
}
|
||||
}
|
||||
|
||||
export class DocxSerializer<S extends Schema = any> {
|
||||
nodes: NodeSerializer<S>;
|
||||
|
||||
marks: MarkSerializer<S>;
|
||||
|
||||
constructor(nodes: NodeSerializer<S>, marks: MarkSerializer<S>) {
|
||||
this.nodes = nodes;
|
||||
this.marks = marks;
|
||||
}
|
||||
|
||||
serialize(content: ProsemirrorNode<S>, options: Options) {
|
||||
const state = new DocxSerializerState<S>(this.nodes, this.marks, options);
|
||||
state.renderContent(content);
|
||||
const doc = createDocFromState(state);
|
||||
return doc;
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import { INumberingOptions } from 'docx';
|
||||
import { IPropertiesOptions } from 'docx/build/file/core-properties';
|
||||
|
||||
export type Mutable<T> = {
|
||||
-readonly [k in keyof T]: T[k];
|
||||
};
|
||||
|
||||
export type IFootnotes = Mutable<Required<IPropertiesOptions>['footnotes']>;
|
||||
export type INumbering = INumberingOptions['config'][0];
|
|
@ -1,47 +0,0 @@
|
|||
import { Document, INumberingOptions, ISectionOptions, Packer, SectionType } from 'docx';
|
||||
import { Node as ProsemirrorNode } from 'prosemirror-model';
|
||||
|
||||
import { IFootnotes } from './types';
|
||||
|
||||
export function createShortId() {
|
||||
return Math.random().toString(36).substr(2, 9);
|
||||
}
|
||||
|
||||
export function createDocFromState(state: {
|
||||
numbering: INumberingOptions['config'];
|
||||
children: ISectionOptions['children'];
|
||||
footnotes?: IFootnotes;
|
||||
}) {
|
||||
const doc = new Document({
|
||||
footnotes: state.footnotes,
|
||||
numbering: {
|
||||
config: state.numbering,
|
||||
},
|
||||
sections: [
|
||||
{
|
||||
properties: {
|
||||
type: SectionType.CONTINUOUS,
|
||||
},
|
||||
children: state.children,
|
||||
},
|
||||
],
|
||||
});
|
||||
return doc;
|
||||
}
|
||||
|
||||
export async function writeDocx(
|
||||
doc: Document,
|
||||
write: ((buffer: Buffer) => void) | ((buffer: Buffer) => Promise<void>)
|
||||
) {
|
||||
const buffer = await Packer.toBuffer(doc);
|
||||
return write(buffer);
|
||||
}
|
||||
|
||||
export function getLatexFromNode(node: ProsemirrorNode): string {
|
||||
let math = '';
|
||||
node.forEach((child) => {
|
||||
if (child.isText) math += child.text;
|
||||
// TODO: improve this as we may have other things in the future
|
||||
});
|
||||
return math;
|
||||
}
|
|
@ -32,6 +32,14 @@ export declare const DocumentApiDefinition: {
|
|||
server: "detail/:id";
|
||||
client: (id: IDocument['id']) => string;
|
||||
};
|
||||
/**
|
||||
* 导出文档
|
||||
*/
|
||||
exportDocx: {
|
||||
method: "post";
|
||||
server: "/export/docx";
|
||||
client: () => string;
|
||||
};
|
||||
/**
|
||||
* 更新文档
|
||||
*/
|
||||
|
|
|
@ -34,6 +34,14 @@ exports.DocumentApiDefinition = {
|
|||
server: 'detail/:id',
|
||||
client: function (id) { return "/document/detail/".concat(id); }
|
||||
},
|
||||
/**
|
||||
* 导出文档
|
||||
*/
|
||||
exportDocx: {
|
||||
method: 'post',
|
||||
server: '/export/docx',
|
||||
client: function () { return '/document/export/docx'; }
|
||||
},
|
||||
/**
|
||||
* 更新文档
|
||||
*/
|
||||
|
|
|
@ -37,6 +37,15 @@ export const DocumentApiDefinition = {
|
|||
client: (id: IDocument['id']) => `/document/detail/${id}`,
|
||||
},
|
||||
|
||||
/**
|
||||
* 导出文档
|
||||
*/
|
||||
exportDocx: {
|
||||
method: 'post' as const,
|
||||
server: '/export/docx' as const,
|
||||
client: () => '/document/export/docx',
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新文档
|
||||
*/
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
"express-rate-limit": "^6.2.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"helmet": "^5.0.2",
|
||||
"html-to-docx": "^1.4.0",
|
||||
"ioredis": "^5.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"mysql2": "^2.3.3",
|
||||
|
|
|
@ -109,6 +109,20 @@ export class DocumentController {
|
|||
return await this.documentService.getDocumentVersion(req.user, documentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出文档
|
||||
* @param req
|
||||
* @param documentId
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post(DocumentApiDefinition.exportDocx.server)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@UseGuards(JwtGuard)
|
||||
async exportDocx(@Body('content') content) {
|
||||
return await this.documentService.exportDocx(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文档成员
|
||||
* @param req
|
||||
|
|
|
@ -17,6 +17,7 @@ import { WikiService } from '@services/wiki.service';
|
|||
import { EMPTY_DOCUMNENT } from '@think/constants';
|
||||
import { AuthEnum, buildMessageURL, DocumentStatus, IUser } from '@think/domains';
|
||||
import { instanceToPlain } from 'class-transformer';
|
||||
import * as HTMLtoDOCX from 'html-to-docx';
|
||||
import * as lodash from 'lodash';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
|
@ -761,4 +762,20 @@ export class DocumentService {
|
|||
.filter(Boolean)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出文档
|
||||
* html-to-docx
|
||||
* @param user
|
||||
* @param content
|
||||
* @returns
|
||||
*/
|
||||
public async exportDocx(content) {
|
||||
const fileBuffer = await HTMLtoDOCX(content, null, {
|
||||
table: { row: { cantSplit: true } },
|
||||
footer: true,
|
||||
pageNumber: true,
|
||||
});
|
||||
return fileBuffer;
|
||||
}
|
||||
}
|
||||
|
|
229
pnpm-lock.yaml
229
pnpm-lock.yaml
|
@ -108,6 +108,7 @@ importers:
|
|||
eslint-plugin-react-hooks: ^4.5.0
|
||||
eslint-plugin-simple-import-sort: ^7.0.0
|
||||
fs-extra: ^10.0.0
|
||||
html-to-docx: ^1.4.0
|
||||
interactjs: ^1.10.11
|
||||
katex: ^0.15.2
|
||||
kity: ^2.0.4
|
||||
|
@ -205,6 +206,7 @@ importers:
|
|||
docx: 7.3.0
|
||||
dompurify: 2.3.5
|
||||
downloadjs: 1.4.7
|
||||
html-to-docx: 1.4.0
|
||||
interactjs: 1.10.11
|
||||
katex: 0.15.2
|
||||
kity: 2.0.4
|
||||
|
@ -326,6 +328,7 @@ importers:
|
|||
express-rate-limit: ^6.2.0
|
||||
fs-extra: ^10.0.0
|
||||
helmet: ^5.0.2
|
||||
html-to-docx: ^1.4.0
|
||||
ioredis: ^5.0.1
|
||||
jest: ^27.2.5
|
||||
lodash: ^4.17.21
|
||||
|
@ -383,6 +386,7 @@ importers:
|
|||
express-rate-limit: 6.2.0_express@4.17.2
|
||||
fs-extra: 10.0.0
|
||||
helmet: 5.0.2
|
||||
html-to-docx: 1.4.0
|
||||
ioredis: 5.0.1
|
||||
lodash: 4.17.21
|
||||
mysql2: 2.3.3
|
||||
|
@ -2615,6 +2619,57 @@ packages:
|
|||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
/@oozcitak/dom/1.15.5:
|
||||
resolution: {integrity: sha512-L6v3Mwb0TaYBYgeYlIeBaHnc+2ZEaDSbFiRm5KmqZQSoBlbPlf+l6aIH/sD5GUf2MYwULw00LT7+dOnEuAEC0A==}
|
||||
engines: {node: '>=8.0'}
|
||||
dependencies:
|
||||
'@oozcitak/infra': 1.0.5
|
||||
'@oozcitak/url': 1.0.0
|
||||
'@oozcitak/util': 8.0.0
|
||||
dev: false
|
||||
|
||||
/@oozcitak/infra/1.0.3:
|
||||
resolution: {integrity: sha512-9O2wxXGnRzy76O1XUxESxDGsXT5kzETJPvYbreO4mv6bqe1+YSuux2cZTagjJ/T4UfEwFJz5ixanOqB0QgYAag==}
|
||||
engines: {node: '>=6.0'}
|
||||
dependencies:
|
||||
'@oozcitak/util': 1.0.1
|
||||
dev: false
|
||||
|
||||
/@oozcitak/infra/1.0.5:
|
||||
resolution: {integrity: sha512-o+zZH7M6l5e3FaAWy3ojaPIVN5eusaYPrKm6MZQt0DKNdgXa2wDYExjpP0t/zx+GoQgQKzLu7cfD8rHCLt8JrQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
dependencies:
|
||||
'@oozcitak/util': 8.0.0
|
||||
dev: false
|
||||
|
||||
/@oozcitak/url/1.0.0:
|
||||
resolution: {integrity: sha512-LGrMeSxeLzsdaitxq3ZmBRVOrlRRQIgNNci6L0VRnOKlJFuRIkNm4B+BObXPCJA6JT5bEJtrrwjn30jueHJYZQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
dependencies:
|
||||
'@oozcitak/infra': 1.0.3
|
||||
'@oozcitak/util': 1.0.2
|
||||
dev: false
|
||||
|
||||
/@oozcitak/util/1.0.1:
|
||||
resolution: {integrity: sha512-dFwFqcKrQnJ2SapOmRD1nQWEZUtbtIy9Y6TyJquzsalWNJsKIPxmTI0KG6Ypyl8j7v89L2wixH9fQDNrF78hKg==}
|
||||
engines: {node: '>=6.0'}
|
||||
dev: false
|
||||
|
||||
/@oozcitak/util/1.0.2:
|
||||
resolution: {integrity: sha512-4n8B1cWlJleSOSba5gxsMcN4tO8KkkcvXhNWW+ADqvq9Xj+Lrl9uCa90GRpjekqQJyt84aUX015DG81LFpZYXA==}
|
||||
engines: {node: '>=6.0'}
|
||||
dev: false
|
||||
|
||||
/@oozcitak/util/8.0.0:
|
||||
resolution: {integrity: sha512-+9Hq6yuoq/3TRV/n/xcpydGBq2qN2/DEDMqNTG7rm95K6ZE2/YY/sPyx62+1n8QsE9O26e5M1URlXsk+AnN9Jw==}
|
||||
engines: {node: '>=6.0'}
|
||||
dev: false
|
||||
|
||||
/@oozcitak/util/8.3.3:
|
||||
resolution: {integrity: sha512-Ufpab7G5PfnEhQyy5kDg9C8ltWJjsVT1P/IYqacjstaqydG4Q21HAT2HUZQYBrC/a1ZLKCz87pfydlDvv8y97w==}
|
||||
engines: {node: '>=6.0'}
|
||||
dev: false
|
||||
|
||||
/@popperjs/core/2.11.2:
|
||||
resolution: {integrity: sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==}
|
||||
dev: false
|
||||
|
@ -4355,6 +4410,10 @@ packages:
|
|||
resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==}
|
||||
dev: true
|
||||
|
||||
/browser-split/0.0.1:
|
||||
resolution: {integrity: sha512-JhvgRb2ihQhsljNda3BI8/UcRHVzrVwo3Q+P8vDtSiyobXuFpuZ9mq+MbRGMnC22CjW3RrfXdg6j6ITX8M+7Ow==}
|
||||
dev: false
|
||||
|
||||
/browserslist/4.19.1:
|
||||
resolution: {integrity: sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==}
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
|
@ -4483,6 +4542,10 @@ packages:
|
|||
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
/camelize/1.0.0:
|
||||
resolution: {integrity: sha512-W2lPwkBkMZwFlPCXhIlYgxu+7gC/NUlCtdK652DAJ1JdgV0sTrvuPFshNPrFa1TY2JOkLhgdeEBplB4ezEa+xg==}
|
||||
dev: false
|
||||
|
||||
/caniuse-lite/1.0.30001304:
|
||||
resolution: {integrity: sha512-bdsfZd6K6ap87AGqSHJP/s1V+U6Z5lyrcbBu3ovbCCf8cSYpwTtGrCBObMpJqwxfTbLW6YTIdbb1jEeTelcpYQ==}
|
||||
|
||||
|
@ -5306,6 +5369,25 @@ packages:
|
|||
xml-js: 1.6.11
|
||||
dev: false
|
||||
|
||||
/dom-serializer/0.2.2:
|
||||
resolution: {integrity: sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==}
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
entities: 2.1.0
|
||||
dev: false
|
||||
|
||||
/dom-walk/0.1.2:
|
||||
resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==}
|
||||
dev: false
|
||||
|
||||
/domelementtype/1.3.1:
|
||||
resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==}
|
||||
dev: false
|
||||
|
||||
/domelementtype/2.3.0:
|
||||
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
|
||||
dev: false
|
||||
|
||||
/domexception/2.0.1:
|
||||
resolution: {integrity: sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -5313,10 +5395,23 @@ packages:
|
|||
webidl-conversions: 5.0.0
|
||||
dev: true
|
||||
|
||||
/domhandler/2.4.2:
|
||||
resolution: {integrity: sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==}
|
||||
dependencies:
|
||||
domelementtype: 1.3.1
|
||||
dev: false
|
||||
|
||||
/dompurify/2.3.5:
|
||||
resolution: {integrity: sha512-kD+f8qEaa42+mjdOpKeztu9Mfx5bv9gVLO6K9jRx4uGvh6Wv06Srn4jr1wPNY2OOUGGSKHNFN+A8MA3v0E0QAQ==}
|
||||
dev: false
|
||||
|
||||
/domutils/1.7.0:
|
||||
resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==}
|
||||
dependencies:
|
||||
dom-serializer: 0.2.2
|
||||
domelementtype: 1.3.1
|
||||
dev: false
|
||||
|
||||
/dot-prop/6.0.1:
|
||||
resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -5424,6 +5519,14 @@ packages:
|
|||
graceful-fs: 4.2.9
|
||||
tapable: 2.2.1
|
||||
|
||||
/ent/2.2.0:
|
||||
resolution: {integrity: sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==}
|
||||
dev: false
|
||||
|
||||
/entities/1.1.2:
|
||||
resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==}
|
||||
dev: false
|
||||
|
||||
/entities/2.1.0:
|
||||
resolution: {integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==}
|
||||
dev: false
|
||||
|
@ -5439,6 +5542,14 @@ packages:
|
|||
is-arrayish: 0.2.1
|
||||
dev: true
|
||||
|
||||
/error/4.4.0:
|
||||
resolution: {integrity: sha512-SNDKualLUtT4StGFP7xNfuFybL2f6iJujFtrWuvJqGbVQGaN+adE23veqzPz1hjUjTunLi2EnJ+0SJxtbJreKw==}
|
||||
dependencies:
|
||||
camelize: 1.0.0
|
||||
string-template: 0.2.1
|
||||
xtend: 4.0.2
|
||||
dev: false
|
||||
|
||||
/es-abstract/1.19.1:
|
||||
resolution: {integrity: sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -5526,7 +5637,7 @@ packages:
|
|||
engines: {node: '>=6'}
|
||||
|
||||
/escape-html/1.0.3:
|
||||
resolution: {integrity: sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=}
|
||||
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||
|
||||
/escape-string-regexp/1.0.5:
|
||||
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
|
||||
|
@ -5845,6 +5956,12 @@ packages:
|
|||
resolution: {integrity: sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
/ev-store/7.0.0:
|
||||
resolution: {integrity: sha512-otazchNRnGzp2YarBJ+GXKVGvhxVATB1zmaStxJBYet0Dyq7A9VhH8IUEB/gRcL6Ch52lfpgPTRJ2m49epyMsQ==}
|
||||
dependencies:
|
||||
individual: 3.0.0
|
||||
dev: false
|
||||
|
||||
/events/3.3.0:
|
||||
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
||||
engines: {node: '>=0.8.x'}
|
||||
|
@ -6416,6 +6533,13 @@ packages:
|
|||
which: 1.3.1
|
||||
dev: true
|
||||
|
||||
/global/4.4.0:
|
||||
resolution: {integrity: sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==}
|
||||
dependencies:
|
||||
min-document: 2.19.0
|
||||
process: 0.11.10
|
||||
dev: false
|
||||
|
||||
/globals/11.12.0:
|
||||
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
|
||||
engines: {node: '>=4'}
|
||||
|
@ -6563,6 +6687,38 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/html-to-docx/1.4.0:
|
||||
resolution: {integrity: sha512-VEbwMb5lwQZiJcnRZLofhziNtQ9UbVE7MQGICZKp896gC6P0ikfU6XRc8g4Z5qmYRBqLBXnY7b6iFYJ72yqNrw==}
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
escape-html: 1.0.3
|
||||
html-to-vdom: 0.7.0
|
||||
image-size: 1.0.2
|
||||
jszip: 3.10.0
|
||||
lodash: 4.17.21
|
||||
nanoid: 3.3.1
|
||||
virtual-dom: 2.1.1
|
||||
xmlbuilder2: 2.1.2
|
||||
dev: false
|
||||
|
||||
/html-to-vdom/0.7.0:
|
||||
resolution: {integrity: sha512-k+d2qNkbx0JO00KezQsNcn6k2I/xSBP4yXYFLvXbcasTTDh+RDLUJS3puxqyNnpdyXWRHFGoKU7cRmby8/APcQ==}
|
||||
dependencies:
|
||||
ent: 2.2.0
|
||||
htmlparser2: 3.10.1
|
||||
dev: false
|
||||
|
||||
/htmlparser2/3.10.1:
|
||||
resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==}
|
||||
dependencies:
|
||||
domelementtype: 1.3.1
|
||||
domhandler: 2.4.2
|
||||
domutils: 1.7.0
|
||||
entities: 1.1.2
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.0
|
||||
dev: false
|
||||
|
||||
/http-errors/1.8.1:
|
||||
resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
@ -6665,6 +6821,14 @@ packages:
|
|||
resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
/image-size/1.0.2:
|
||||
resolution: {integrity: sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
queue: 6.0.2
|
||||
dev: false
|
||||
|
||||
/immediate/3.0.6:
|
||||
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
|
||||
dev: false
|
||||
|
@ -6701,6 +6865,10 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/individual/3.0.0:
|
||||
resolution: {integrity: sha512-rUY5vtT748NMRbEMrTNiFfy29BgGZwGXUi2NFUVMWQrogSLzlJvQV9eeMWi+g1aVaQ53tpyLAQtd5x/JH0Nh1g==}
|
||||
dev: false
|
||||
|
||||
/inflight/1.0.6:
|
||||
resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=}
|
||||
dependencies:
|
||||
|
@ -6918,6 +7086,10 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/is-object/1.0.2:
|
||||
resolution: {integrity: sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==}
|
||||
dev: false
|
||||
|
||||
/is-path-cwd/2.2.0:
|
||||
resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
@ -8319,6 +8491,12 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/min-document/2.19.0:
|
||||
resolution: {integrity: sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==}
|
||||
dependencies:
|
||||
dom-walk: 0.1.2
|
||||
dev: false
|
||||
|
||||
/min-indent/1.0.1:
|
||||
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
|
||||
engines: {node: '>=4'}
|
||||
|
@ -8500,6 +8678,10 @@ packages:
|
|||
- webpack
|
||||
dev: false
|
||||
|
||||
/next-tick/0.2.2:
|
||||
resolution: {integrity: sha512-f7h4svPtl+QidoBv4taKXUjJ70G2asaZ8G28nS0OkqaalX8dwwrtWtyxEDPK62AC00ur/+/E0pUwBwY5EPn15Q==}
|
||||
dev: false
|
||||
|
||||
/next/12.1.0_sfoxds7t5ydpegc3knd667wn6m:
|
||||
resolution: {integrity: sha512-s885kWvnIlxsUFHq9UGyIyLiuD0G3BUC/xrH0CEnH5lHEWkwQcHOORgbDF0hbrW9vr/7am4ETfX4A7M6DjrE7Q==}
|
||||
engines: {node: '>=12.22.0'}
|
||||
|
@ -9264,6 +9446,11 @@ packages:
|
|||
resolution: {integrity: sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==}
|
||||
dev: false
|
||||
|
||||
/process/0.11.10:
|
||||
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
|
||||
engines: {node: '>= 0.6.0'}
|
||||
dev: false
|
||||
|
||||
/prompts/2.4.2:
|
||||
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
|
||||
engines: {node: '>= 6'}
|
||||
|
@ -9466,6 +9653,12 @@ packages:
|
|||
/queue-microtask/1.2.3:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
|
||||
/queue/6.0.2:
|
||||
resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==}
|
||||
dependencies:
|
||||
inherits: 2.0.4
|
||||
dev: false
|
||||
|
||||
/quick-format-unescaped/4.0.4:
|
||||
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
|
||||
dev: false
|
||||
|
@ -10471,6 +10664,10 @@ packages:
|
|||
strip-ansi: 6.0.1
|
||||
dev: true
|
||||
|
||||
/string-template/0.2.1:
|
||||
resolution: {integrity: sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==}
|
||||
dev: false
|
||||
|
||||
/string-width/4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -11558,6 +11755,19 @@ packages:
|
|||
resolution: {integrity: sha512-CjMt64yC9D+XUx2t3F0TPbh/Yt5+/ke8/s3IizXa6NtksdJUFDoCcNxi/KRZ9eiZPR/D77pHnnQzAtCoLDaGIw==}
|
||||
dev: false
|
||||
|
||||
/virtual-dom/2.1.1:
|
||||
resolution: {integrity: sha512-wb6Qc9Lbqug0kRqo/iuApfBpJJAq14Sk1faAnSmtqXiwahg7PVTvWMs9L02Z8nNIMqbwsxzBAA90bbtRLbw0zg==}
|
||||
dependencies:
|
||||
browser-split: 0.0.1
|
||||
error: 4.4.0
|
||||
ev-store: 7.0.0
|
||||
global: 4.4.0
|
||||
is-object: 1.0.2
|
||||
next-tick: 0.2.2
|
||||
x-is-array: 0.1.0
|
||||
x-is-string: 0.1.0
|
||||
dev: false
|
||||
|
||||
/vm2/3.9.5:
|
||||
resolution: {integrity: sha512-LuCAHZN75H9tdrAiLFf030oW7nJV5xwNMuk1ymOZwopmuK3d2H4L1Kv4+GFHgarKiLfXXLFU+7LDABHnwOkWng==}
|
||||
engines: {node: '>=6.0'}
|
||||
|
@ -11995,6 +12205,14 @@ packages:
|
|||
optional: true
|
||||
dev: false
|
||||
|
||||
/x-is-array/0.1.0:
|
||||
resolution: {integrity: sha512-goHPif61oNrr0jJgsXRfc8oqtYzvfiMJpTqwE7Z4y9uH+T3UozkGqQ4d2nX9mB9khvA8U2o/UbPOFjgC7hLWIA==}
|
||||
dev: false
|
||||
|
||||
/x-is-string/0.1.0:
|
||||
resolution: {integrity: sha512-GojqklwG8gpzOVEVki5KudKNoq7MbbjYZCbyWzEz7tyPA7eleiE0+ePwOWQQRb5fm86rD3S8Tc0tSFf3AOv50w==}
|
||||
dev: false
|
||||
|
||||
/xml-js/1.6.11:
|
||||
resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==}
|
||||
hasBin: true
|
||||
|
@ -12023,6 +12241,15 @@ packages:
|
|||
engines: {node: '>=4.0'}
|
||||
dev: false
|
||||
|
||||
/xmlbuilder2/2.1.2:
|
||||
resolution: {integrity: sha512-PI710tmtVlQ5VmwzbRTuhmVhKnj9pM8Si+iOZCV2g2SNo3gCrpzR2Ka9wNzZtqfD+mnP+xkrqoNy0sjKZqP4Dg==}
|
||||
engines: {node: '>=8.0'}
|
||||
dependencies:
|
||||
'@oozcitak/dom': 1.15.5
|
||||
'@oozcitak/infra': 1.0.5
|
||||
'@oozcitak/util': 8.3.3
|
||||
dev: false
|
||||
|
||||
/xmlchars/2.2.0:
|
||||
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
|
||||
dev: true
|
||||
|
|
Loading…
Reference in New Issue