Compare commits

..

15 Commits

27 changed files with 265 additions and 169 deletions

View File

@ -1,7 +1,7 @@
{
"name": "client",
"private": true,
"version": "0.2.7",
"version": "0.2.8",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",

View File

@ -23,7 +23,7 @@ const RoleButton = forwardRef<HTMLButtonElement, RoleButtonProps>(
),
);
interface SpaceRoleMenuProps {
interface RoleMenuProps {
roles: IRoleData[];
roleName: string;
onChange?: (value: string) => void;
@ -35,7 +35,7 @@ export default function RoleSelectMenu({
roleName,
onChange,
disabled,
}: SpaceRoleMenuProps) {
}: RoleMenuProps) {
return (
<Menu withArrow>
<Menu.Target>

View File

@ -4,13 +4,14 @@ import { TextAlign } from "@tiptap/extension-text-align";
import { TaskList } from "@tiptap/extension-task-list";
import { TaskItem } from "@tiptap/extension-task-item";
import { Underline } from "@tiptap/extension-underline";
import { Link } from "@tiptap/extension-link";
import { Superscript } from "@tiptap/extension-superscript";
import SubScript from "@tiptap/extension-subscript";
import { Highlight } from "@tiptap/extension-highlight";
import { Typography } from "@tiptap/extension-typography";
import { TextStyle } from "@tiptap/extension-text-style";
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 SlashCommand from "@/features/editor/extensions/slash-command";
import { Collaboration } from "@tiptap/extension-collaboration";
@ -23,8 +24,6 @@ import {
DetailsSummary,
MathBlock,
MathInline,
Table,
TableHeader,
TableCell,
TableRow,
TrailingNode,
@ -66,7 +65,9 @@ export const mainExtensions = [
if (node.type.name === "detailsSummary") {
return "Toggle title";
}
return 'Write anything. Enter "/" for commands';
if (node.type.name === "paragraph") {
return 'Write anything. Enter "/" for commands';
}
},
includeChildren: true,
}),
@ -95,10 +96,16 @@ export const mainExtensions = [
class: "comment-mark",
},
}),
Table,
Table.configure({
resizable: true,
lastColumnResizable: false,
allowTableNodeSelection: true,
}),
TableRow,
TableCell,
TableHeader,
MathInline.configure({
view: MathInlineView,
}),

View File

@ -56,7 +56,7 @@ export default function PageExportModal({
<div>
<Text size="md">Format</Text>
</div>
<ExportFormatSelection onChange={handleChange} />
<ExportFormatSelection format={format} onChange={handleChange} />
</Group>
<Group justify="center" mt="md">
@ -72,16 +72,17 @@ export default function PageExportModal({
}
interface ExportFormatSelection {
format: ExportFormat;
onChange: (value: string) => void;
}
function ExportFormatSelection({ onChange }: ExportFormatSelection) {
function ExportFormatSelection({ format, onChange }: ExportFormatSelection) {
return (
<Select
data={[
{ value: "markdown", label: "Markdown" },
{ value: "html", label: "HTML" },
]}
defaultValue={ExportFormat.Markdown}
defaultValue={format}
onChange={onChange}
styles={{ wrapper: { maxWidth: 120 } }}
comboboxProps={{ width: "120" }}

View File

@ -84,14 +84,14 @@ function ImportFormatSelection({ spaceId, onClose }: ImportFormatSelection) {
}
}
const newTreeNodes = buildTree(pages);
const fullTree = treeData.concat(newTreeNodes);
if (pages?.length > 0 && pageCount > 0) {
const newTreeNodes = buildTree(pages);
const fullTree = treeData.concat(newTreeNodes);
if (newTreeNodes?.length && fullTree?.length > 0) {
setTreeData(fullTree);
}
if (newTreeNodes?.length && fullTree?.length > 0) {
setTreeData(fullTree);
}
if (pageCount > 0) {
const pageCountText = pageCount === 1 ? "1 page" : `${pageCount} pages`;
notifications.update({

View File

@ -11,11 +11,14 @@ import {
userRoleData,
} from "@/features/workspace/types/user-role-data.ts";
import useUserRole from "@/hooks/use-user-role.tsx";
import { UserRole } from "@/lib/types.ts";
export default function WorkspaceMembersTable() {
const { data, isLoading } = useWorkspaceMembersQuery({ limit: 100 });
const changeMemberRoleMutation = useChangeMemberRoleMutation();
const { isAdmin } = useUserRole();
const { isAdmin, isOwner } = useUserRole();
const assignableUserRoles = isOwner ? userRoleData : userRoleData.filter((role) => role.value !== UserRole.OWNER);
const handleRoleChange = async (
userId: string,
@ -69,7 +72,7 @@ export default function WorkspaceMembersTable() {
<Table.Td>
<RoleSelectMenu
roles={userRoleData}
roles={assignableUserRoles}
roleName={getUserRoleLabel(user.role)}
onChange={(newRole) =>
handleRoleChange(user.id, user.role, newRole)

View File

@ -53,9 +53,10 @@ export function useChangeMemberRoleMutation() {
return useMutation<any, Error, any>({
mutationFn: (data) => changeMemberRole(data),
onSuccess: (data, variables) => {
// TODO: change in cache instead
notifications.show({ message: "Member role updated successfully" });
queryClient.refetchQueries({
queryKey: ["workspaceMembers", variables.spaceId],
queryKey: ["workspaceMembers"],
});
},
onError: (error) => {

View File

@ -1,6 +1,6 @@
{
"name": "server",
"version": "0.2.7",
"version": "0.2.8",
"description": "",
"author": "",
"private": true,

View File

@ -10,6 +10,8 @@ import { Typography } from '@tiptap/extension-typography';
import { TextStyle } from '@tiptap/extension-text-style';
import { Color } from '@tiptap/extension-color';
import { Youtube } from '@tiptap/extension-youtube';
import Table from '@tiptap/extension-table';
import TableHeader from '@tiptap/extension-table-header';
import {
Callout,
Comment,
@ -19,16 +21,18 @@ import {
LinkExtension,
MathBlock,
MathInline,
Table,
TableCell,
TableHeader,
TableRow,
TiptapImage,
TiptapVideo,
TrailingNode,
} from '@docmost/editor-ext';
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 = [
StarterKit,

View File

@ -59,7 +59,7 @@ export class AttachmentService {
});
} catch (err) {
// delete uploaded file on error
console.error(err);
this.logger.error(err);
}
return attachment;

View File

@ -1,5 +1,6 @@
import {
BadRequestException,
ForbiddenException,
Injectable,
NotFoundException,
} from '@nestjs/common';
@ -217,11 +218,21 @@ export class WorkspaceService {
) {
const user = await this.userRepo.findById(userRoleDto.userId, workspaceId);
const newRole = userRoleDto.role.toLowerCase();
if (!user) {
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;
}
@ -238,7 +249,7 @@ export class WorkspaceService {
await this.userRepo.updateUser(
{
role: userRoleDto.role,
role: newRole,
},
user.id,
workspaceId,

View File

@ -18,11 +18,11 @@ export function turndown(html: string): string {
highlightedCodeBlock,
taskList,
callout,
toggleListTitle,
toggleListBody,
preserveDetail,
listParagraph,
mathInline,
mathBlock,
]);
return turndownService.turndown(html).replaceAll('<br>', ' ');
}
@ -72,29 +72,51 @@ function taskList(turndownService: TurndownService) {
});
}
function toggleListTitle(turndownService: TurndownService) {
turndownService.addRule('toggleListTitle', {
function preserveDetail(turndownService: TurndownService) {
turndownService.addRule('preserveDetail', {
filter: function (node: HTMLInputElement) {
return (
node.nodeName === 'SUMMARY' && node.parentNode.nodeName === 'DETAILS'
);
return node.nodeName === 'DETAILS';
},
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) {
turndownService.addRule('toggleListContent', {
function mathInline(turndownService: TurndownService) {
turndownService.addRule('mathInline', {
filter: function (node: HTMLInputElement) {
return (
node.getAttribute('data-type') === 'detailsContent' &&
node.parentNode.nodeName === 'DETAILS'
node.nodeName === 'SPAN' &&
node.getAttribute('data-type') === 'mathInline'
);
},
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`;
},
});
}

View File

@ -20,6 +20,7 @@ import {
} from '../../core/casl/interfaces/space-ability.type';
import { FileInterceptor } from '../../common/interceptors/file.interceptor';
import * as bytes from 'bytes';
import * as path from 'path';
import { MAX_FILE_SIZE } from '../../core/attachment/attachment.constants';
import { ImportService } from './import.service';
import { AuthWorkspace } from '../../common/decorators/auth-workspace.decorator';
@ -42,6 +43,8 @@ export class ImportController {
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
) {
const validFileExtensions = ['.md', '.html'];
const maxFileSize = bytes(MAX_FILE_SIZE);
let file = null;
@ -62,6 +65,12 @@ export class ImportController {
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;
if (!spaceId) {

View File

@ -3,13 +3,17 @@ import { PageRepo } from '@docmost/db/repos/page/page.repo';
import { MultipartFile } from '@fastify/multipart';
import { sanitize } from 'sanitize-filename-ts';
import * as path from 'path';
import { htmlToJson } from '../../collaboration/collaboration.util';
import { marked } from 'marked';
import {
htmlToJson,
tiptapExtensions,
} from '../../collaboration/collaboration.util';
import { InjectKysely } from 'nestjs-kysely';
import { KyselyDB } from '@docmost/db/types/kysely.types';
import { generateSlugId } from '../../common/helpers';
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()
export class ImportService {
@ -36,16 +40,23 @@ export class ImportService {
let prosemirrorState = null;
let createdPage = null;
if (fileExtension.endsWith('.md') && fileMimeType === 'text/markdown') {
prosemirrorState = await this.processMarkdown(fileContent);
}
if (fileExtension.endsWith('.html') && fileMimeType === 'text/html') {
prosemirrorState = await this.processHTML(fileContent);
try {
if (fileExtension.endsWith('.md') && fileMimeType === 'text/markdown') {
prosemirrorState = await this.processMarkdown(fileContent);
} else if (
fileExtension.endsWith('.html') &&
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) {
const message = 'Unsupported file format or mime type';
const message = 'Failed to create ProseMirror state';
this.logger.error(message);
throw new BadRequestException(message);
}
@ -63,14 +74,19 @@ export class ImportService {
slugId: generateSlugId(),
title: pageTitle,
content: prosemirrorJson,
ydoc: await this.createYdoc(prosemirrorJson),
position: pagePosition,
spaceId: spaceId,
creatorId: userId,
workspaceId: workspaceId,
lastUpdatedById: userId,
});
this.logger.debug(
`Successfully imported "${title}${fileExtension}. ID: ${createdPage.id} - SlugId: ${createdPage.slugId}"`,
);
} catch (err) {
const message = 'Failed to create page';
const message = 'Failed to create imported page';
this.logger.error(message, err);
throw new BadRequestException(message);
}
@ -80,14 +96,37 @@ export class ImportService {
}
async processMarkdown(markdownInput: string): Promise<any> {
// turn markdown to html
const html = await marked.parse(markdownInput);
return await this.processHTML(html);
try {
const html = await markdownToHtml(markdownInput);
return this.processHTML(html);
} catch (err) {
throw err;
}
}
async processHTML(htmlInput: string): Promise<any> {
// turn html to prosemirror state
return htmlToJson(transformHTML(htmlInput));
try {
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) {

View File

@ -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));
}

View File

@ -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);
}

View File

@ -1,7 +1,7 @@
{
"name": "docmost",
"homepage": "https://docmost.com",
"version": "0.2.7",
"version": "0.2.8",
"private": true,
"scripts": {
"build": "nx run-many -t build",
@ -53,6 +53,7 @@
"@tiptap/extension-typography": "^2.5.4",
"@tiptap/extension-underline": "^2.5.4",
"@tiptap/extension-youtube": "^2.5.4",
"@tiptap/html": "^2.5.4",
"@tiptap/pm": "^2.5.4",
"@tiptap/react": "^2.5.4",
"@tiptap/starter-kit": "^2.5.4",

View File

@ -1,7 +1,7 @@
import { type EditorState, Plugin, PluginKey } from "@tiptap/pm/state";
import { Decoration, DecorationSet } from "@tiptap/pm/view";
import { IAttachment } from "client/src/lib/types";
import { MediaUploadOptions, UploadFn } from "../media-utils";
import { IAttachment } from "../types";
const uploadKey = new PluginKey("image-upload");

View File

@ -36,7 +36,9 @@ export const MathBlock = Node.create({
return {
text: {
default: "",
parseHTML: (element) => element.innerHTML.split("$")[1],
parseHTML: (element) => {
return element.innerHTML;
},
},
};
},
@ -44,7 +46,7 @@ export const MathBlock = Node.create({
parseHTML() {
return [
{
tag: "div",
tag: `div[data-type="${this.name}"]`,
getAttrs: (node: HTMLElement) => {
return node.hasAttribute("data-katex") ? {} : false;
},
@ -55,8 +57,8 @@ export const MathBlock = Node.create({
renderHTML({ HTMLAttributes }) {
return [
"div",
{},
["div", { "data-katex": true }, `$$${HTMLAttributes.text}$$`],
{ "data-type": this.name, "data-katex": true },
`${HTMLAttributes.text}`,
];
},

View File

@ -37,7 +37,9 @@ export const MathInline = Node.create<MathInlineOption>({
return {
text: {
default: "",
parseHTML: (element) => element.innerHTML.split("$")[1],
parseHTML: (element) => {
return element.innerHTML;
},
},
};
},
@ -45,7 +47,7 @@ export const MathInline = Node.create<MathInlineOption>({
parseHTML() {
return [
{
tag: "span",
tag: `span[data-type="${this.name}"]`,
getAttrs: (node: HTMLElement) => {
return node.hasAttribute("data-katex") ? {} : false;
},
@ -54,7 +56,11 @@ export const MathInline = Node.create<MathInlineOption>({
},
renderHTML({ HTMLAttributes }) {
return ["span", { "data-katex": true }, `$${HTMLAttributes.text}$` || {}];
return [
"span",
{ "data-type": this.name, "data-katex": true },
`${HTMLAttributes.text}`,
];
},
addNodeView() {

View File

@ -1,3 +0,0 @@
import TiptapTableHeader from "@tiptap/extension-table-header";
export const TableHeader = TiptapTableHeader.configure();

View File

@ -1,4 +1,2 @@
export * from "./table-extension";
export * from "./header";
export * from "./row";
export * from "./cell";

View File

@ -1,7 +0,0 @@
import TiptapTable from "@tiptap/extension-table";
export const Table = TiptapTable.configure({
resizable: true,
lastColumnResizable: false,
allowTableNodeSelection: true,
});

View File

@ -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;
}

View File

@ -3,7 +3,7 @@ import { Editor, findParentNode, isTextSelection } from "@tiptap/core";
import { Selection, Transaction } from "@tiptap/pm/state";
import { CellSelection, TableMap } from "@tiptap/pm/tables";
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) => {
const map = TableMap.get(selection.$anchorCell.node(-1));

View File

@ -1,7 +1,7 @@
import { type EditorState, Plugin, PluginKey } from "@tiptap/pm/state";
import { Decoration, DecorationSet } from "@tiptap/pm/view";
import { IAttachment } from "client/src/lib/types";
import { MediaUploadOptions, UploadFn } from "../media-utils";
import { IAttachment } from "../types";
const uploadKey = new PluginKey("video-upload");

47
pnpm-lock.yaml generated
View File

@ -119,6 +119,9 @@ importers:
'@tiptap/extension-youtube':
specifier: ^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':
specifier: ^2.5.4
version: 2.5.4
@ -3780,6 +3783,12 @@ packages:
peerDependencies:
'@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':
resolution: {integrity: sha512-oFIsuniptdUXn93x4aM2sVN3hYKo9Fj55zAkYrWhwxFYUYcPxd5ibra2we+wRK5TaiPu098wpC+yMSTZ/KKMpA==}
@ -4757,6 +4766,10 @@ packages:
css-to-mat@1.1.1:
resolution: {integrity: sha512-kvpxFYZb27jRd2vium35G7q5XZ2WJ9rWjDUMNT36M3Hc41qCrLXFM5iEKMGXcrPsKfXEN+8l/riB4QzwwwiEyQ==}
css-what@6.1.0:
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
engines: {node: '>= 6'}
cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
@ -6178,8 +6191,8 @@ packages:
makeerror@1.0.12:
resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==}
markdown-it@14.0.0:
resolution: {integrity: sha512-seFjF0FIcPt4P9U39Bq1JYblX0KZCjDLFFQPHpL5AzHpqPEKtosxmdq/LTVZnjfH7tjt9BxStm+wXcDBNuYmzw==}
markdown-it@14.1.0:
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
hasBin: true
marked@13.0.2:
@ -7790,8 +7803,8 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
uc.micro@2.0.0:
resolution: {integrity: sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig==}
uc.micro@2.1.0:
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
uid2@1.0.0:
resolution: {integrity: sha512-+I6aJUv63YAcY9n4mQreLUt0d4lvwkkopDNmpomkAUz0fAkEMV9pRWxN0EjhW1YfRhcuyHg2v3mwddCDW1+LFQ==}
@ -8160,6 +8173,10 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
zeed-dom@0.10.11:
resolution: {integrity: sha512-7ukbu6aQKx34OQ7PfUIxOuAhk2MvyZY/t4/IJsVzy76zuMzfhE74+Dbyp8SHiUJPTPkF0FflP1KVrGJ7gk9IHw==}
engines: {node: '>=14.13.1'}
zod@3.23.8:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
@ -12059,6 +12076,12 @@ snapshots:
dependencies:
'@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':
dependencies:
prosemirror-changeset: 2.2.1
@ -13272,6 +13295,8 @@ snapshots:
'@daybrush/utils': 1.13.0
'@scena/matrix': 1.1.1
css-what@6.1.0: {}
cssesc@3.0.0: {}
cssstyle@3.0.0:
@ -14899,7 +14924,7 @@ snapshots:
linkify-it@5.0.0:
dependencies:
uc.micro: 2.0.0
uc.micro: 2.1.0
linkifyjs@4.1.3: {}
@ -14994,14 +15019,14 @@ snapshots:
dependencies:
tmpl: 1.0.5
markdown-it@14.0.0:
markdown-it@14.1.0:
dependencies:
argparse: 2.0.1
entities: 4.5.0
linkify-it: 5.0.0
mdurl: 2.0.0
punycode.js: 2.3.1
uc.micro: 2.0.0
uc.micro: 2.1.0
marked@13.0.2: {}
@ -15690,7 +15715,7 @@ snapshots:
prosemirror-markdown@1.13.0:
dependencies:
markdown-it: 14.0.0
markdown-it: 14.1.0
prosemirror-model: 1.22.1
prosemirror-menu@1.2.4:
@ -16778,7 +16803,7 @@ snapshots:
typescript@5.5.2: {}
uc.micro@2.0.0: {}
uc.micro@2.1.0: {}
uid2@1.0.0: {}
@ -17101,4 +17126,8 @@ snapshots:
yocto-queue@0.1.0: {}
zeed-dom@0.10.11:
dependencies:
css-what: 6.1.0
zod@3.23.8: {}