mirror of
https://github.com/docmost/docmost.git
synced 2026-05-15 13:14:11 +08:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 48e76aa9f4 | |||
| 2bd6422a35 | |||
| 407a1aff3b | |||
| b4bc184cb3 | |||
| 109dbdbe02 | |||
| 2df7de5828 | |||
| 373fc86e47 | |||
| 5052a9ea40 | |||
| cd47c79d86 | |||
| 78746938b7 | |||
| 4d2936627c | |||
| d2ecd28047 | |||
| bb92ca75e9 | |||
| 8f3e2ff663 | |||
| 89f6311e46 |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.2.7",
|
"version": "0.2.8",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const RoleButton = forwardRef<HTMLButtonElement, RoleButtonProps>(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
interface SpaceRoleMenuProps {
|
interface RoleMenuProps {
|
||||||
roles: IRoleData[];
|
roles: IRoleData[];
|
||||||
roleName: string;
|
roleName: string;
|
||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
@@ -35,7 +35,7 @@ export default function RoleSelectMenu({
|
|||||||
roleName,
|
roleName,
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
disabled,
|
||||||
}: SpaceRoleMenuProps) {
|
}: RoleMenuProps) {
|
||||||
return (
|
return (
|
||||||
<Menu withArrow>
|
<Menu withArrow>
|
||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import { TextAlign } from "@tiptap/extension-text-align";
|
|||||||
import { TaskList } from "@tiptap/extension-task-list";
|
import { TaskList } from "@tiptap/extension-task-list";
|
||||||
import { TaskItem } from "@tiptap/extension-task-item";
|
import { TaskItem } from "@tiptap/extension-task-item";
|
||||||
import { Underline } from "@tiptap/extension-underline";
|
import { Underline } from "@tiptap/extension-underline";
|
||||||
import { Link } from "@tiptap/extension-link";
|
|
||||||
import { Superscript } from "@tiptap/extension-superscript";
|
import { Superscript } from "@tiptap/extension-superscript";
|
||||||
import SubScript from "@tiptap/extension-subscript";
|
import SubScript from "@tiptap/extension-subscript";
|
||||||
import { Highlight } from "@tiptap/extension-highlight";
|
import { Highlight } from "@tiptap/extension-highlight";
|
||||||
import { Typography } from "@tiptap/extension-typography";
|
import { Typography } from "@tiptap/extension-typography";
|
||||||
import { TextStyle } from "@tiptap/extension-text-style";
|
import { TextStyle } from "@tiptap/extension-text-style";
|
||||||
import { Color } from "@tiptap/extension-color";
|
import { Color } from "@tiptap/extension-color";
|
||||||
|
import Table from "@tiptap/extension-table";
|
||||||
|
import TableHeader from "@tiptap/extension-table-header";
|
||||||
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
|
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
|
||||||
import SlashCommand from "@/features/editor/extensions/slash-command";
|
import SlashCommand from "@/features/editor/extensions/slash-command";
|
||||||
import { Collaboration } from "@tiptap/extension-collaboration";
|
import { Collaboration } from "@tiptap/extension-collaboration";
|
||||||
@@ -23,8 +24,6 @@ import {
|
|||||||
DetailsSummary,
|
DetailsSummary,
|
||||||
MathBlock,
|
MathBlock,
|
||||||
MathInline,
|
MathInline,
|
||||||
Table,
|
|
||||||
TableHeader,
|
|
||||||
TableCell,
|
TableCell,
|
||||||
TableRow,
|
TableRow,
|
||||||
TrailingNode,
|
TrailingNode,
|
||||||
@@ -66,7 +65,9 @@ export const mainExtensions = [
|
|||||||
if (node.type.name === "detailsSummary") {
|
if (node.type.name === "detailsSummary") {
|
||||||
return "Toggle title";
|
return "Toggle title";
|
||||||
}
|
}
|
||||||
return 'Write anything. Enter "/" for commands';
|
if (node.type.name === "paragraph") {
|
||||||
|
return 'Write anything. Enter "/" for commands';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
includeChildren: true,
|
includeChildren: true,
|
||||||
}),
|
}),
|
||||||
@@ -95,10 +96,16 @@ export const mainExtensions = [
|
|||||||
class: "comment-mark",
|
class: "comment-mark",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
Table,
|
|
||||||
|
Table.configure({
|
||||||
|
resizable: true,
|
||||||
|
lastColumnResizable: false,
|
||||||
|
allowTableNodeSelection: true,
|
||||||
|
}),
|
||||||
TableRow,
|
TableRow,
|
||||||
TableCell,
|
TableCell,
|
||||||
TableHeader,
|
TableHeader,
|
||||||
|
|
||||||
MathInline.configure({
|
MathInline.configure({
|
||||||
view: MathInlineView,
|
view: MathInlineView,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export default function PageExportModal({
|
|||||||
<div>
|
<div>
|
||||||
<Text size="md">Format</Text>
|
<Text size="md">Format</Text>
|
||||||
</div>
|
</div>
|
||||||
<ExportFormatSelection onChange={handleChange} />
|
<ExportFormatSelection format={format} onChange={handleChange} />
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Group justify="center" mt="md">
|
<Group justify="center" mt="md">
|
||||||
@@ -72,16 +72,17 @@ export default function PageExportModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ExportFormatSelection {
|
interface ExportFormatSelection {
|
||||||
|
format: ExportFormat;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
}
|
}
|
||||||
function ExportFormatSelection({ onChange }: ExportFormatSelection) {
|
function ExportFormatSelection({ format, onChange }: ExportFormatSelection) {
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
data={[
|
data={[
|
||||||
{ value: "markdown", label: "Markdown" },
|
{ value: "markdown", label: "Markdown" },
|
||||||
{ value: "html", label: "HTML" },
|
{ value: "html", label: "HTML" },
|
||||||
]}
|
]}
|
||||||
defaultValue={ExportFormat.Markdown}
|
defaultValue={format}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
styles={{ wrapper: { maxWidth: 120 } }}
|
styles={{ wrapper: { maxWidth: 120 } }}
|
||||||
comboboxProps={{ width: "120" }}
|
comboboxProps={{ width: "120" }}
|
||||||
|
|||||||
@@ -84,14 +84,14 @@ function ImportFormatSelection({ spaceId, onClose }: ImportFormatSelection) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const newTreeNodes = buildTree(pages);
|
if (pages?.length > 0 && pageCount > 0) {
|
||||||
const fullTree = treeData.concat(newTreeNodes);
|
const newTreeNodes = buildTree(pages);
|
||||||
|
const fullTree = treeData.concat(newTreeNodes);
|
||||||
|
|
||||||
if (newTreeNodes?.length && fullTree?.length > 0) {
|
if (newTreeNodes?.length && fullTree?.length > 0) {
|
||||||
setTreeData(fullTree);
|
setTreeData(fullTree);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pageCount > 0) {
|
|
||||||
const pageCountText = pageCount === 1 ? "1 page" : `${pageCount} pages`;
|
const pageCountText = pageCount === 1 ? "1 page" : `${pageCount} pages`;
|
||||||
|
|
||||||
notifications.update({
|
notifications.update({
|
||||||
|
|||||||
+5
-2
@@ -11,11 +11,14 @@ import {
|
|||||||
userRoleData,
|
userRoleData,
|
||||||
} from "@/features/workspace/types/user-role-data.ts";
|
} from "@/features/workspace/types/user-role-data.ts";
|
||||||
import useUserRole from "@/hooks/use-user-role.tsx";
|
import useUserRole from "@/hooks/use-user-role.tsx";
|
||||||
|
import { UserRole } from "@/lib/types.ts";
|
||||||
|
|
||||||
export default function WorkspaceMembersTable() {
|
export default function WorkspaceMembersTable() {
|
||||||
const { data, isLoading } = useWorkspaceMembersQuery({ limit: 100 });
|
const { data, isLoading } = useWorkspaceMembersQuery({ limit: 100 });
|
||||||
const changeMemberRoleMutation = useChangeMemberRoleMutation();
|
const changeMemberRoleMutation = useChangeMemberRoleMutation();
|
||||||
const { isAdmin } = useUserRole();
|
const { isAdmin, isOwner } = useUserRole();
|
||||||
|
|
||||||
|
const assignableUserRoles = isOwner ? userRoleData : userRoleData.filter((role) => role.value !== UserRole.OWNER);
|
||||||
|
|
||||||
const handleRoleChange = async (
|
const handleRoleChange = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
@@ -69,7 +72,7 @@ export default function WorkspaceMembersTable() {
|
|||||||
|
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<RoleSelectMenu
|
<RoleSelectMenu
|
||||||
roles={userRoleData}
|
roles={assignableUserRoles}
|
||||||
roleName={getUserRoleLabel(user.role)}
|
roleName={getUserRoleLabel(user.role)}
|
||||||
onChange={(newRole) =>
|
onChange={(newRole) =>
|
||||||
handleRoleChange(user.id, user.role, newRole)
|
handleRoleChange(user.id, user.role, newRole)
|
||||||
|
|||||||
@@ -53,9 +53,10 @@ export function useChangeMemberRoleMutation() {
|
|||||||
return useMutation<any, Error, any>({
|
return useMutation<any, Error, any>({
|
||||||
mutationFn: (data) => changeMemberRole(data),
|
mutationFn: (data) => changeMemberRole(data),
|
||||||
onSuccess: (data, variables) => {
|
onSuccess: (data, variables) => {
|
||||||
|
// TODO: change in cache instead
|
||||||
notifications.show({ message: "Member role updated successfully" });
|
notifications.show({ message: "Member role updated successfully" });
|
||||||
queryClient.refetchQueries({
|
queryClient.refetchQueries({
|
||||||
queryKey: ["workspaceMembers", variables.spaceId],
|
queryKey: ["workspaceMembers"],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.2.7",
|
"version": "0.2.8",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import { Typography } from '@tiptap/extension-typography';
|
|||||||
import { TextStyle } from '@tiptap/extension-text-style';
|
import { TextStyle } from '@tiptap/extension-text-style';
|
||||||
import { Color } from '@tiptap/extension-color';
|
import { Color } from '@tiptap/extension-color';
|
||||||
import { Youtube } from '@tiptap/extension-youtube';
|
import { Youtube } from '@tiptap/extension-youtube';
|
||||||
|
import Table from '@tiptap/extension-table';
|
||||||
|
import TableHeader from '@tiptap/extension-table-header';
|
||||||
import {
|
import {
|
||||||
Callout,
|
Callout,
|
||||||
Comment,
|
Comment,
|
||||||
@@ -19,16 +21,18 @@ import {
|
|||||||
LinkExtension,
|
LinkExtension,
|
||||||
MathBlock,
|
MathBlock,
|
||||||
MathInline,
|
MathInline,
|
||||||
Table,
|
|
||||||
TableCell,
|
TableCell,
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
TableRow,
|
||||||
TiptapImage,
|
TiptapImage,
|
||||||
TiptapVideo,
|
TiptapVideo,
|
||||||
TrailingNode,
|
TrailingNode,
|
||||||
} from '@docmost/editor-ext';
|
} from '@docmost/editor-ext';
|
||||||
import { generateText, JSONContent } from '@tiptap/core';
|
import { generateText, JSONContent } from '@tiptap/core';
|
||||||
import { generateHTML, generateJSON } from '../common/helpers/prosemirror/html';
|
import { generateHTML } from '../common/helpers/prosemirror/html';
|
||||||
|
// @tiptap/html library works best for generating prosemirror json state but not HTML
|
||||||
|
// see: https://github.com/ueberdosis/tiptap/issues/5352
|
||||||
|
// see:https://github.com/ueberdosis/tiptap/issues/4089
|
||||||
|
import { generateJSON } from '@tiptap/html';
|
||||||
|
|
||||||
export const tiptapExtensions = [
|
export const tiptapExtensions = [
|
||||||
StarterKit,
|
StarterKit,
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export class AttachmentService {
|
|||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// delete uploaded file on error
|
// delete uploaded file on error
|
||||||
console.error(err);
|
this.logger.error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
return attachment;
|
return attachment;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
|
ForbiddenException,
|
||||||
Injectable,
|
Injectable,
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
@@ -217,11 +218,21 @@ export class WorkspaceService {
|
|||||||
) {
|
) {
|
||||||
const user = await this.userRepo.findById(userRoleDto.userId, workspaceId);
|
const user = await this.userRepo.findById(userRoleDto.userId, workspaceId);
|
||||||
|
|
||||||
|
const newRole = userRoleDto.role.toLowerCase();
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new BadRequestException('Workspace member not found');
|
throw new BadRequestException('Workspace member not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.role === userRoleDto.role) {
|
// prevent ADMIN from managing OWNER role
|
||||||
|
if (
|
||||||
|
(authUser.role === UserRole.ADMIN && newRole === UserRole.OWNER) ||
|
||||||
|
(authUser.role === UserRole.ADMIN && user.role === UserRole.OWNER)
|
||||||
|
) {
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.role === newRole) {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +249,7 @@ export class WorkspaceService {
|
|||||||
|
|
||||||
await this.userRepo.updateUser(
|
await this.userRepo.updateUser(
|
||||||
{
|
{
|
||||||
role: userRoleDto.role,
|
role: newRole,
|
||||||
},
|
},
|
||||||
user.id,
|
user.id,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ export function turndown(html: string): string {
|
|||||||
highlightedCodeBlock,
|
highlightedCodeBlock,
|
||||||
taskList,
|
taskList,
|
||||||
callout,
|
callout,
|
||||||
toggleListTitle,
|
preserveDetail,
|
||||||
toggleListBody,
|
|
||||||
listParagraph,
|
listParagraph,
|
||||||
|
mathInline,
|
||||||
|
mathBlock,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return turndownService.turndown(html).replaceAll('<br>', ' ');
|
return turndownService.turndown(html).replaceAll('<br>', ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,29 +72,51 @@ function taskList(turndownService: TurndownService) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleListTitle(turndownService: TurndownService) {
|
function preserveDetail(turndownService: TurndownService) {
|
||||||
turndownService.addRule('toggleListTitle', {
|
turndownService.addRule('preserveDetail', {
|
||||||
filter: function (node: HTMLInputElement) {
|
filter: function (node: HTMLInputElement) {
|
||||||
return (
|
return node.nodeName === 'DETAILS';
|
||||||
node.nodeName === 'SUMMARY' && node.parentNode.nodeName === 'DETAILS'
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
replacement: function (content: any, node: HTMLInputElement) {
|
replacement: function (content: any, node: HTMLInputElement) {
|
||||||
return '- ' + content;
|
// TODO: preserve summary of nested details
|
||||||
|
const summary = node.querySelector(':scope > summary');
|
||||||
|
let detailSummary = '';
|
||||||
|
|
||||||
|
if (summary) {
|
||||||
|
detailSummary = `<summary>${turndownService.turndown(summary.innerHTML)}</summary>`;
|
||||||
|
summary.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const detailsContent = turndownService.turndown(node.innerHTML);
|
||||||
|
return `\n<details>\n${detailSummary}\n\n${detailsContent}\n\n</details>\n`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleListBody(turndownService: TurndownService) {
|
function mathInline(turndownService: TurndownService) {
|
||||||
turndownService.addRule('toggleListContent', {
|
turndownService.addRule('mathInline', {
|
||||||
filter: function (node: HTMLInputElement) {
|
filter: function (node: HTMLInputElement) {
|
||||||
return (
|
return (
|
||||||
node.getAttribute('data-type') === 'detailsContent' &&
|
node.nodeName === 'SPAN' &&
|
||||||
node.parentNode.nodeName === 'DETAILS'
|
node.getAttribute('data-type') === 'mathInline'
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
replacement: function (content: any, node: HTMLInputElement) {
|
replacement: function (content: any, node: HTMLInputElement) {
|
||||||
return ` ${content.replace(/\n/g, '\n ')} `;
|
return `$${content}$`;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function mathBlock(turndownService: TurndownService) {
|
||||||
|
turndownService.addRule('mathBlock', {
|
||||||
|
filter: function (node: HTMLInputElement) {
|
||||||
|
return (
|
||||||
|
node.nodeName === 'DIV' &&
|
||||||
|
node.getAttribute('data-type') === 'mathBlock'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
replacement: function (content: any, node: HTMLInputElement) {
|
||||||
|
return `\n$$${content}$$\n`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
} from '../../core/casl/interfaces/space-ability.type';
|
} from '../../core/casl/interfaces/space-ability.type';
|
||||||
import { FileInterceptor } from '../../common/interceptors/file.interceptor';
|
import { FileInterceptor } from '../../common/interceptors/file.interceptor';
|
||||||
import * as bytes from 'bytes';
|
import * as bytes from 'bytes';
|
||||||
|
import * as path from 'path';
|
||||||
import { MAX_FILE_SIZE } from '../../core/attachment/attachment.constants';
|
import { MAX_FILE_SIZE } from '../../core/attachment/attachment.constants';
|
||||||
import { ImportService } from './import.service';
|
import { ImportService } from './import.service';
|
||||||
import { AuthWorkspace } from '../../common/decorators/auth-workspace.decorator';
|
import { AuthWorkspace } from '../../common/decorators/auth-workspace.decorator';
|
||||||
@@ -42,6 +43,8 @@ export class ImportController {
|
|||||||
@AuthUser() user: User,
|
@AuthUser() user: User,
|
||||||
@AuthWorkspace() workspace: Workspace,
|
@AuthWorkspace() workspace: Workspace,
|
||||||
) {
|
) {
|
||||||
|
const validFileExtensions = ['.md', '.html'];
|
||||||
|
|
||||||
const maxFileSize = bytes(MAX_FILE_SIZE);
|
const maxFileSize = bytes(MAX_FILE_SIZE);
|
||||||
|
|
||||||
let file = null;
|
let file = null;
|
||||||
@@ -62,6 +65,12 @@ export class ImportController {
|
|||||||
throw new BadRequestException('Failed to upload file');
|
throw new BadRequestException('Failed to upload file');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!validFileExtensions.includes(path.extname(file.filename).toLowerCase())
|
||||||
|
) {
|
||||||
|
throw new BadRequestException('Invalid import file type.');
|
||||||
|
}
|
||||||
|
|
||||||
const spaceId = file.fields?.spaceId?.value;
|
const spaceId = file.fields?.spaceId?.value;
|
||||||
|
|
||||||
if (!spaceId) {
|
if (!spaceId) {
|
||||||
|
|||||||
@@ -3,13 +3,17 @@ import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
|||||||
import { MultipartFile } from '@fastify/multipart';
|
import { MultipartFile } from '@fastify/multipart';
|
||||||
import { sanitize } from 'sanitize-filename-ts';
|
import { sanitize } from 'sanitize-filename-ts';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { htmlToJson } from '../../collaboration/collaboration.util';
|
import {
|
||||||
import { marked } from 'marked';
|
htmlToJson,
|
||||||
|
tiptapExtensions,
|
||||||
|
} from '../../collaboration/collaboration.util';
|
||||||
import { InjectKysely } from 'nestjs-kysely';
|
import { InjectKysely } from 'nestjs-kysely';
|
||||||
import { KyselyDB } from '@docmost/db/types/kysely.types';
|
import { KyselyDB } from '@docmost/db/types/kysely.types';
|
||||||
import { generateSlugId } from '../../common/helpers';
|
import { generateSlugId } from '../../common/helpers';
|
||||||
import { generateJitteredKeyBetween } from 'fractional-indexing-jittered';
|
import { generateJitteredKeyBetween } from 'fractional-indexing-jittered';
|
||||||
import { transformHTML } from './utils/html.utils';
|
import { markdownToHtml } from './utils/marked.utils';
|
||||||
|
import { TiptapTransformer } from '@hocuspocus/transformer';
|
||||||
|
import * as Y from 'yjs';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ImportService {
|
export class ImportService {
|
||||||
@@ -36,16 +40,23 @@ export class ImportService {
|
|||||||
let prosemirrorState = null;
|
let prosemirrorState = null;
|
||||||
let createdPage = null;
|
let createdPage = null;
|
||||||
|
|
||||||
if (fileExtension.endsWith('.md') && fileMimeType === 'text/markdown') {
|
try {
|
||||||
prosemirrorState = await this.processMarkdown(fileContent);
|
if (fileExtension.endsWith('.md') && fileMimeType === 'text/markdown') {
|
||||||
}
|
prosemirrorState = await this.processMarkdown(fileContent);
|
||||||
|
} else if (
|
||||||
if (fileExtension.endsWith('.html') && fileMimeType === 'text/html') {
|
fileExtension.endsWith('.html') &&
|
||||||
prosemirrorState = await this.processHTML(fileContent);
|
fileMimeType === 'text/html'
|
||||||
|
) {
|
||||||
|
prosemirrorState = await this.processHTML(fileContent);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const message = 'Error processing file content';
|
||||||
|
this.logger.error(message, err);
|
||||||
|
throw new BadRequestException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!prosemirrorState) {
|
if (!prosemirrorState) {
|
||||||
const message = 'Unsupported file format or mime type';
|
const message = 'Failed to create ProseMirror state';
|
||||||
this.logger.error(message);
|
this.logger.error(message);
|
||||||
throw new BadRequestException(message);
|
throw new BadRequestException(message);
|
||||||
}
|
}
|
||||||
@@ -63,14 +74,19 @@ export class ImportService {
|
|||||||
slugId: generateSlugId(),
|
slugId: generateSlugId(),
|
||||||
title: pageTitle,
|
title: pageTitle,
|
||||||
content: prosemirrorJson,
|
content: prosemirrorJson,
|
||||||
|
ydoc: await this.createYdoc(prosemirrorJson),
|
||||||
position: pagePosition,
|
position: pagePosition,
|
||||||
spaceId: spaceId,
|
spaceId: spaceId,
|
||||||
creatorId: userId,
|
creatorId: userId,
|
||||||
workspaceId: workspaceId,
|
workspaceId: workspaceId,
|
||||||
lastUpdatedById: userId,
|
lastUpdatedById: userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.logger.debug(
|
||||||
|
`Successfully imported "${title}${fileExtension}. ID: ${createdPage.id} - SlugId: ${createdPage.slugId}"`,
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = 'Failed to create page';
|
const message = 'Failed to create imported page';
|
||||||
this.logger.error(message, err);
|
this.logger.error(message, err);
|
||||||
throw new BadRequestException(message);
|
throw new BadRequestException(message);
|
||||||
}
|
}
|
||||||
@@ -80,14 +96,37 @@ export class ImportService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async processMarkdown(markdownInput: string): Promise<any> {
|
async processMarkdown(markdownInput: string): Promise<any> {
|
||||||
// turn markdown to html
|
try {
|
||||||
const html = await marked.parse(markdownInput);
|
const html = await markdownToHtml(markdownInput);
|
||||||
return await this.processHTML(html);
|
return this.processHTML(html);
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async processHTML(htmlInput: string): Promise<any> {
|
async processHTML(htmlInput: string): Promise<any> {
|
||||||
// turn html to prosemirror state
|
try {
|
||||||
return htmlToJson(transformHTML(htmlInput));
|
return htmlToJson(htmlInput);
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createYdoc(prosemirrorJson: any): Promise<Buffer | null> {
|
||||||
|
if (prosemirrorJson) {
|
||||||
|
this.logger.debug(`Converting prosemirror json state to ydoc`);
|
||||||
|
|
||||||
|
const ydoc = TiptapTransformer.toYdoc(
|
||||||
|
prosemirrorJson,
|
||||||
|
'default',
|
||||||
|
tiptapExtensions,
|
||||||
|
);
|
||||||
|
|
||||||
|
Y.encodeStateAsUpdate(ydoc);
|
||||||
|
|
||||||
|
return Buffer.from(Y.encodeStateAsUpdate(ydoc));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
extractTitleAndRemoveHeading(prosemirrorState: any) {
|
extractTitleAndRemoveHeading(prosemirrorState: any) {
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
import { Window, DOMParser } from 'happy-dom';
|
|
||||||
|
|
||||||
function transformTaskList(html: string): string {
|
|
||||||
const window = new Window();
|
|
||||||
const doc = new DOMParser(window).parseFromString(html, 'text/html');
|
|
||||||
|
|
||||||
const ulElements = doc.querySelectorAll('ul');
|
|
||||||
ulElements.forEach((ul) => {
|
|
||||||
let isTaskList = false;
|
|
||||||
|
|
||||||
const liElements = ul.querySelectorAll('li');
|
|
||||||
liElements.forEach((li) => {
|
|
||||||
const checkbox = li.querySelector('input[type="checkbox"]');
|
|
||||||
|
|
||||||
if (checkbox) {
|
|
||||||
isTaskList = true;
|
|
||||||
// Add taskItem data type
|
|
||||||
li.setAttribute('data-type', 'taskItem');
|
|
||||||
// Set data-checked attribute based on the checkbox state
|
|
||||||
// @ts-ignore
|
|
||||||
li.setAttribute('data-checked', checkbox.checked ? 'true' : 'false');
|
|
||||||
// Remove the checkbox from the li
|
|
||||||
checkbox.remove();
|
|
||||||
|
|
||||||
// Move the content of <p> out of the <p> and remove <p>
|
|
||||||
const pElements = li.querySelectorAll('p');
|
|
||||||
pElements.forEach((p) => {
|
|
||||||
// Append the content of the <p> element to its parent (the <li> element)
|
|
||||||
while (p.firstChild) {
|
|
||||||
li.appendChild(p.firstChild);
|
|
||||||
}
|
|
||||||
// Remove the now empty <p> element
|
|
||||||
p.remove();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// If any <li> contains a checkbox, mark the <ul> as a task list
|
|
||||||
if (isTaskList) {
|
|
||||||
ul.setAttribute('data-type', 'taskList');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return doc.body.innerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
function transformCallouts(html: string): string {
|
|
||||||
const window = new Window();
|
|
||||||
const doc = new DOMParser(window).parseFromString(html, 'text/html');
|
|
||||||
|
|
||||||
const calloutRegex = /:::(\w+)\s*([\s\S]*?)\s*:::/g;
|
|
||||||
|
|
||||||
const createCalloutDiv = (type: string, content: string): HTMLElement => {
|
|
||||||
const div = doc.createElement('div');
|
|
||||||
div.setAttribute('data-type', 'callout');
|
|
||||||
div.setAttribute('data-callout-type', type);
|
|
||||||
const p = doc.createElement('p');
|
|
||||||
p.textContent = content.trim();
|
|
||||||
div.appendChild(p);
|
|
||||||
return div as unknown as HTMLElement;
|
|
||||||
};
|
|
||||||
|
|
||||||
const pElements = doc.querySelectorAll('p');
|
|
||||||
|
|
||||||
pElements.forEach((p) => {
|
|
||||||
if (calloutRegex.test(p.innerHTML) && !p.closest('ul, ol')) {
|
|
||||||
calloutRegex.lastIndex = 0;
|
|
||||||
const [, type, content] = calloutRegex.exec(p.innerHTML) || [];
|
|
||||||
const calloutDiv = createCalloutDiv(type, content);
|
|
||||||
// @ts-ignore
|
|
||||||
p.replaceWith(calloutDiv);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return doc.body.innerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function transformHTML(html: string): string {
|
|
||||||
return transformTaskList(transformCallouts(html));
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { marked } from 'marked';
|
||||||
|
|
||||||
|
marked.use({
|
||||||
|
renderer: {
|
||||||
|
// @ts-ignore
|
||||||
|
list(body: string, isOrdered: boolean, start: number) {
|
||||||
|
if (isOrdered) {
|
||||||
|
const startAttr = start !== 1 ? ` start="${start}"` : '';
|
||||||
|
return `<ol ${startAttr}>\n${body}</ol>\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataType = body.includes(`<input`) ? ' data-type="taskList"' : '';
|
||||||
|
return `<ul${dataType}>\n${body}</ul>\n`;
|
||||||
|
},
|
||||||
|
// @ts-ignore
|
||||||
|
listitem({ text, raw, task: isTask, checked: isChecked }): string {
|
||||||
|
if (!isTask) {
|
||||||
|
return `<li>${text}</li>\n`;
|
||||||
|
}
|
||||||
|
const checkedAttr = isChecked
|
||||||
|
? 'data-checked="true"'
|
||||||
|
: 'data-checked="false"';
|
||||||
|
return `<li data-type="taskItem" ${checkedAttr}>${text}</li>\n`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function markdownToHtml(markdownInput: string): Promise<string> {
|
||||||
|
const YAML_FONT_MATTER_REGEX = /^\s*---[\s\S]*?---\s*/;
|
||||||
|
|
||||||
|
const markdown = markdownInput
|
||||||
|
.replace(YAML_FONT_MATTER_REGEX, '')
|
||||||
|
.trimStart();
|
||||||
|
|
||||||
|
return marked.parse(markdown);
|
||||||
|
}
|
||||||
+2
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "docmost",
|
"name": "docmost",
|
||||||
"homepage": "https://docmost.com",
|
"homepage": "https://docmost.com",
|
||||||
"version": "0.2.7",
|
"version": "0.2.8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nx run-many -t build",
|
"build": "nx run-many -t build",
|
||||||
@@ -53,6 +53,7 @@
|
|||||||
"@tiptap/extension-typography": "^2.5.4",
|
"@tiptap/extension-typography": "^2.5.4",
|
||||||
"@tiptap/extension-underline": "^2.5.4",
|
"@tiptap/extension-underline": "^2.5.4",
|
||||||
"@tiptap/extension-youtube": "^2.5.4",
|
"@tiptap/extension-youtube": "^2.5.4",
|
||||||
|
"@tiptap/html": "^2.5.4",
|
||||||
"@tiptap/pm": "^2.5.4",
|
"@tiptap/pm": "^2.5.4",
|
||||||
"@tiptap/react": "^2.5.4",
|
"@tiptap/react": "^2.5.4",
|
||||||
"@tiptap/starter-kit": "^2.5.4",
|
"@tiptap/starter-kit": "^2.5.4",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { type EditorState, Plugin, PluginKey } from "@tiptap/pm/state";
|
import { type EditorState, Plugin, PluginKey } from "@tiptap/pm/state";
|
||||||
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
||||||
import { IAttachment } from "client/src/lib/types";
|
|
||||||
import { MediaUploadOptions, UploadFn } from "../media-utils";
|
import { MediaUploadOptions, UploadFn } from "../media-utils";
|
||||||
|
import { IAttachment } from "../types";
|
||||||
|
|
||||||
const uploadKey = new PluginKey("image-upload");
|
const uploadKey = new PluginKey("image-upload");
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ export const MathBlock = Node.create({
|
|||||||
return {
|
return {
|
||||||
text: {
|
text: {
|
||||||
default: "",
|
default: "",
|
||||||
parseHTML: (element) => element.innerHTML.split("$")[1],
|
parseHTML: (element) => {
|
||||||
|
return element.innerHTML;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -44,7 +46,7 @@ export const MathBlock = Node.create({
|
|||||||
parseHTML() {
|
parseHTML() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
tag: "div",
|
tag: `div[data-type="${this.name}"]`,
|
||||||
getAttrs: (node: HTMLElement) => {
|
getAttrs: (node: HTMLElement) => {
|
||||||
return node.hasAttribute("data-katex") ? {} : false;
|
return node.hasAttribute("data-katex") ? {} : false;
|
||||||
},
|
},
|
||||||
@@ -55,8 +57,8 @@ export const MathBlock = Node.create({
|
|||||||
renderHTML({ HTMLAttributes }) {
|
renderHTML({ HTMLAttributes }) {
|
||||||
return [
|
return [
|
||||||
"div",
|
"div",
|
||||||
{},
|
{ "data-type": this.name, "data-katex": true },
|
||||||
["div", { "data-katex": true }, `$$${HTMLAttributes.text}$$`],
|
`${HTMLAttributes.text}`,
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ export const MathInline = Node.create<MathInlineOption>({
|
|||||||
return {
|
return {
|
||||||
text: {
|
text: {
|
||||||
default: "",
|
default: "",
|
||||||
parseHTML: (element) => element.innerHTML.split("$")[1],
|
parseHTML: (element) => {
|
||||||
|
return element.innerHTML;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -45,7 +47,7 @@ export const MathInline = Node.create<MathInlineOption>({
|
|||||||
parseHTML() {
|
parseHTML() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
tag: "span",
|
tag: `span[data-type="${this.name}"]`,
|
||||||
getAttrs: (node: HTMLElement) => {
|
getAttrs: (node: HTMLElement) => {
|
||||||
return node.hasAttribute("data-katex") ? {} : false;
|
return node.hasAttribute("data-katex") ? {} : false;
|
||||||
},
|
},
|
||||||
@@ -54,7 +56,11 @@ export const MathInline = Node.create<MathInlineOption>({
|
|||||||
},
|
},
|
||||||
|
|
||||||
renderHTML({ HTMLAttributes }) {
|
renderHTML({ HTMLAttributes }) {
|
||||||
return ["span", { "data-katex": true }, `$${HTMLAttributes.text}$` || {}];
|
return [
|
||||||
|
"span",
|
||||||
|
{ "data-type": this.name, "data-katex": true },
|
||||||
|
`${HTMLAttributes.text}`,
|
||||||
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
addNodeView() {
|
addNodeView() {
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
import TiptapTableHeader from "@tiptap/extension-table-header";
|
|
||||||
|
|
||||||
export const TableHeader = TiptapTableHeader.configure();
|
|
||||||
@@ -1,4 +1,2 @@
|
|||||||
export * from "./table-extension";
|
|
||||||
export * from "./header";
|
|
||||||
export * from "./row";
|
export * from "./row";
|
||||||
export * from "./cell";
|
export * from "./cell";
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import TiptapTable from "@tiptap/extension-table";
|
|
||||||
|
|
||||||
export const Table = TiptapTable.configure({
|
|
||||||
resizable: true,
|
|
||||||
lastColumnResizable: false,
|
|
||||||
allowTableNodeSelection: true,
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// repetition for now
|
||||||
|
export interface IAttachment {
|
||||||
|
id: string;
|
||||||
|
fileName: string;
|
||||||
|
filePath: string;
|
||||||
|
fileSize: number;
|
||||||
|
fileExt: string;
|
||||||
|
mimeType: string;
|
||||||
|
type: string;
|
||||||
|
creatorId: string;
|
||||||
|
pageId: string | null;
|
||||||
|
spaceId: string | null;
|
||||||
|
workspaceId: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
deletedAt: string | null;
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import { Editor, findParentNode, isTextSelection } from "@tiptap/core";
|
|||||||
import { Selection, Transaction } from "@tiptap/pm/state";
|
import { Selection, Transaction } from "@tiptap/pm/state";
|
||||||
import { CellSelection, TableMap } from "@tiptap/pm/tables";
|
import { CellSelection, TableMap } from "@tiptap/pm/tables";
|
||||||
import { Node, ResolvedPos } from "@tiptap/pm/model";
|
import { Node, ResolvedPos } from "@tiptap/pm/model";
|
||||||
import { Table } from "./table/table-extension";
|
import Table from "@tiptap/extension-table";
|
||||||
|
|
||||||
export const isRectSelected = (rect: any) => (selection: CellSelection) => {
|
export const isRectSelected = (rect: any) => (selection: CellSelection) => {
|
||||||
const map = TableMap.get(selection.$anchorCell.node(-1));
|
const map = TableMap.get(selection.$anchorCell.node(-1));
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { type EditorState, Plugin, PluginKey } from "@tiptap/pm/state";
|
import { type EditorState, Plugin, PluginKey } from "@tiptap/pm/state";
|
||||||
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
||||||
import { IAttachment } from "client/src/lib/types";
|
|
||||||
import { MediaUploadOptions, UploadFn } from "../media-utils";
|
import { MediaUploadOptions, UploadFn } from "../media-utils";
|
||||||
|
import { IAttachment } from "../types";
|
||||||
|
|
||||||
const uploadKey = new PluginKey("video-upload");
|
const uploadKey = new PluginKey("video-upload");
|
||||||
|
|
||||||
|
|||||||
Generated
+38
-9
@@ -119,6 +119,9 @@ importers:
|
|||||||
'@tiptap/extension-youtube':
|
'@tiptap/extension-youtube':
|
||||||
specifier: ^2.5.4
|
specifier: ^2.5.4
|
||||||
version: 2.5.4(@tiptap/core@2.5.4(@tiptap/pm@2.5.4))
|
version: 2.5.4(@tiptap/core@2.5.4(@tiptap/pm@2.5.4))
|
||||||
|
'@tiptap/html':
|
||||||
|
specifier: ^2.5.4
|
||||||
|
version: 2.5.4(@tiptap/core@2.5.4(@tiptap/pm@2.5.4))(@tiptap/pm@2.5.4)
|
||||||
'@tiptap/pm':
|
'@tiptap/pm':
|
||||||
specifier: ^2.5.4
|
specifier: ^2.5.4
|
||||||
version: 2.5.4
|
version: 2.5.4
|
||||||
@@ -3780,6 +3783,12 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tiptap/core': ^2.5.4
|
'@tiptap/core': ^2.5.4
|
||||||
|
|
||||||
|
'@tiptap/html@2.5.4':
|
||||||
|
resolution: {integrity: sha512-Fcvsa7kkO+Id7WBFimDN5zdHksVGVnyHnffaN/PaAgbKmzP53BC38Pd0XuHS+KL6btqQIFE2GlqNYnyIos7i+g==}
|
||||||
|
peerDependencies:
|
||||||
|
'@tiptap/core': ^2.5.4
|
||||||
|
'@tiptap/pm': ^2.5.4
|
||||||
|
|
||||||
'@tiptap/pm@2.5.4':
|
'@tiptap/pm@2.5.4':
|
||||||
resolution: {integrity: sha512-oFIsuniptdUXn93x4aM2sVN3hYKo9Fj55zAkYrWhwxFYUYcPxd5ibra2we+wRK5TaiPu098wpC+yMSTZ/KKMpA==}
|
resolution: {integrity: sha512-oFIsuniptdUXn93x4aM2sVN3hYKo9Fj55zAkYrWhwxFYUYcPxd5ibra2we+wRK5TaiPu098wpC+yMSTZ/KKMpA==}
|
||||||
|
|
||||||
@@ -4757,6 +4766,10 @@ packages:
|
|||||||
css-to-mat@1.1.1:
|
css-to-mat@1.1.1:
|
||||||
resolution: {integrity: sha512-kvpxFYZb27jRd2vium35G7q5XZ2WJ9rWjDUMNT36M3Hc41qCrLXFM5iEKMGXcrPsKfXEN+8l/riB4QzwwwiEyQ==}
|
resolution: {integrity: sha512-kvpxFYZb27jRd2vium35G7q5XZ2WJ9rWjDUMNT36M3Hc41qCrLXFM5iEKMGXcrPsKfXEN+8l/riB4QzwwwiEyQ==}
|
||||||
|
|
||||||
|
css-what@6.1.0:
|
||||||
|
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
cssesc@3.0.0:
|
cssesc@3.0.0:
|
||||||
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -6178,8 +6191,8 @@ packages:
|
|||||||
makeerror@1.0.12:
|
makeerror@1.0.12:
|
||||||
resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==}
|
resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==}
|
||||||
|
|
||||||
markdown-it@14.0.0:
|
markdown-it@14.1.0:
|
||||||
resolution: {integrity: sha512-seFjF0FIcPt4P9U39Bq1JYblX0KZCjDLFFQPHpL5AzHpqPEKtosxmdq/LTVZnjfH7tjt9BxStm+wXcDBNuYmzw==}
|
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
marked@13.0.2:
|
marked@13.0.2:
|
||||||
@@ -7790,8 +7803,8 @@ packages:
|
|||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=14.17'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
uc.micro@2.0.0:
|
uc.micro@2.1.0:
|
||||||
resolution: {integrity: sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig==}
|
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
|
||||||
|
|
||||||
uid2@1.0.0:
|
uid2@1.0.0:
|
||||||
resolution: {integrity: sha512-+I6aJUv63YAcY9n4mQreLUt0d4lvwkkopDNmpomkAUz0fAkEMV9pRWxN0EjhW1YfRhcuyHg2v3mwddCDW1+LFQ==}
|
resolution: {integrity: sha512-+I6aJUv63YAcY9n4mQreLUt0d4lvwkkopDNmpomkAUz0fAkEMV9pRWxN0EjhW1YfRhcuyHg2v3mwddCDW1+LFQ==}
|
||||||
@@ -8160,6 +8173,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
zeed-dom@0.10.11:
|
||||||
|
resolution: {integrity: sha512-7ukbu6aQKx34OQ7PfUIxOuAhk2MvyZY/t4/IJsVzy76zuMzfhE74+Dbyp8SHiUJPTPkF0FflP1KVrGJ7gk9IHw==}
|
||||||
|
engines: {node: '>=14.13.1'}
|
||||||
|
|
||||||
zod@3.23.8:
|
zod@3.23.8:
|
||||||
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
|
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
|
||||||
|
|
||||||
@@ -12059,6 +12076,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 2.5.4(@tiptap/pm@2.5.4)
|
'@tiptap/core': 2.5.4(@tiptap/pm@2.5.4)
|
||||||
|
|
||||||
|
'@tiptap/html@2.5.4(@tiptap/core@2.5.4(@tiptap/pm@2.5.4))(@tiptap/pm@2.5.4)':
|
||||||
|
dependencies:
|
||||||
|
'@tiptap/core': 2.5.4(@tiptap/pm@2.5.4)
|
||||||
|
'@tiptap/pm': 2.5.4
|
||||||
|
zeed-dom: 0.10.11
|
||||||
|
|
||||||
'@tiptap/pm@2.5.4':
|
'@tiptap/pm@2.5.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
prosemirror-changeset: 2.2.1
|
prosemirror-changeset: 2.2.1
|
||||||
@@ -13272,6 +13295,8 @@ snapshots:
|
|||||||
'@daybrush/utils': 1.13.0
|
'@daybrush/utils': 1.13.0
|
||||||
'@scena/matrix': 1.1.1
|
'@scena/matrix': 1.1.1
|
||||||
|
|
||||||
|
css-what@6.1.0: {}
|
||||||
|
|
||||||
cssesc@3.0.0: {}
|
cssesc@3.0.0: {}
|
||||||
|
|
||||||
cssstyle@3.0.0:
|
cssstyle@3.0.0:
|
||||||
@@ -14899,7 +14924,7 @@ snapshots:
|
|||||||
|
|
||||||
linkify-it@5.0.0:
|
linkify-it@5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
uc.micro: 2.0.0
|
uc.micro: 2.1.0
|
||||||
|
|
||||||
linkifyjs@4.1.3: {}
|
linkifyjs@4.1.3: {}
|
||||||
|
|
||||||
@@ -14994,14 +15019,14 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tmpl: 1.0.5
|
tmpl: 1.0.5
|
||||||
|
|
||||||
markdown-it@14.0.0:
|
markdown-it@14.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
argparse: 2.0.1
|
argparse: 2.0.1
|
||||||
entities: 4.5.0
|
entities: 4.5.0
|
||||||
linkify-it: 5.0.0
|
linkify-it: 5.0.0
|
||||||
mdurl: 2.0.0
|
mdurl: 2.0.0
|
||||||
punycode.js: 2.3.1
|
punycode.js: 2.3.1
|
||||||
uc.micro: 2.0.0
|
uc.micro: 2.1.0
|
||||||
|
|
||||||
marked@13.0.2: {}
|
marked@13.0.2: {}
|
||||||
|
|
||||||
@@ -15690,7 +15715,7 @@ snapshots:
|
|||||||
|
|
||||||
prosemirror-markdown@1.13.0:
|
prosemirror-markdown@1.13.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
markdown-it: 14.0.0
|
markdown-it: 14.1.0
|
||||||
prosemirror-model: 1.22.1
|
prosemirror-model: 1.22.1
|
||||||
|
|
||||||
prosemirror-menu@1.2.4:
|
prosemirror-menu@1.2.4:
|
||||||
@@ -16778,7 +16803,7 @@ snapshots:
|
|||||||
|
|
||||||
typescript@5.5.2: {}
|
typescript@5.5.2: {}
|
||||||
|
|
||||||
uc.micro@2.0.0: {}
|
uc.micro@2.1.0: {}
|
||||||
|
|
||||||
uid2@1.0.0: {}
|
uid2@1.0.0: {}
|
||||||
|
|
||||||
@@ -17101,4 +17126,8 @@ snapshots:
|
|||||||
|
|
||||||
yocto-queue@0.1.0: {}
|
yocto-queue@0.1.0: {}
|
||||||
|
|
||||||
|
zeed-dom@0.10.11:
|
||||||
|
dependencies:
|
||||||
|
css-what: 6.1.0
|
||||||
|
|
||||||
zod@3.23.8: {}
|
zod@3.23.8: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user