feat: add tiptap markdown support, add checklist support from trello (#245)

* feat: add tiptap markdown support, add checklist support from trello

* feat(api): implement bulk creation of checklists and items
This commit is contained in:
exception-raised
2025-11-17 22:31:57 +02:00
committed by GitHub
parent c30da618ec
commit c0a3c37d95
5 changed files with 356 additions and 51 deletions

View File

@@ -65,6 +65,7 @@
"superjson": "2.2.1", "superjson": "2.2.1",
"tailwind-merge": "^2.5.2", "tailwind-merge": "^2.5.2",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"tiptap-markdown": "^0.8",
"zod": "catalog:" "zod": "catalog:"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -7,6 +7,7 @@ import type { Instance as TippyInstance } from "tippy.js";
import { Button } from "@headlessui/react"; import { Button } from "@headlessui/react";
import { t } from "@lingui/core/macro"; import { t } from "@lingui/core/macro";
import Link from "@tiptap/extension-link"; import Link from "@tiptap/extension-link";
import Mention from "@tiptap/extension-mention";
import Placeholder from "@tiptap/extension-placeholder"; import Placeholder from "@tiptap/extension-placeholder";
import { import {
BubbleMenu, BubbleMenu,
@@ -39,9 +40,10 @@ import {
} from "react-icons/hi2"; } from "react-icons/hi2";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import tippy from "tippy.js"; import tippy from "tippy.js";
import Mention from "@tiptap/extension-mention"; import { Markdown } from "tiptap-markdown";
import Avatar from "./Avatar";
import { getAvatarUrl } from "~/utils/helpers"; import { getAvatarUrl } from "~/utils/helpers";
import Avatar from "./Avatar";
declare module "@tiptap/core" { declare module "@tiptap/core" {
interface Commands<ReturnType> { interface Commands<ReturnType> {
@@ -77,7 +79,7 @@ export interface RenderSuggestionsProps {
command: (item: SlashCommandItem) => void; command: (item: SlashCommandItem) => void;
} }
export type WorkspaceMember = { export interface WorkspaceMember {
publicId: string; publicId: string;
user: { user: {
id: string; id: string;
@@ -85,7 +87,7 @@ export type WorkspaceMember = {
image: string | null; image: string | null;
} | null; } | null;
email: string; email: string;
}; }
const CommandsList = forwardRef< const CommandsList = forwardRef<
{ onKeyDown: (props: SuggestionKeyDownProps) => boolean }, { onKeyDown: (props: SuggestionKeyDownProps) => boolean },
@@ -200,7 +202,11 @@ const RenderSuggestions = () => {
}; };
}; };
type MentionItem = { id: string; label: string; image: string | null }; interface MentionItem {
id: string;
label: string;
image: string | null;
}
const MentionList = forwardRef< const MentionList = forwardRef<
{ onKeyDown: (props: SuggestionKeyDownProps) => boolean }, { onKeyDown: (props: SuggestionKeyDownProps) => boolean },
@@ -238,30 +244,32 @@ const MentionList = forwardRef<
return ( return (
<div className="w-56 rounded-md border-[1px] border-light-200 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200"> <div className="w-56 rounded-md border-[1px] border-light-200 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200">
<div className="max-h-[350px] overflow-y-auto p-1 scrollbar-thin scrollbar-thumb-light-200 dark:scrollbar-thumb-dark-300"> <div className="max-h-[350px] overflow-y-auto p-1 scrollbar-thin scrollbar-thumb-light-200 dark:scrollbar-thumb-dark-300">
{items.length > 0 ? items.map((item, index) => ( {items.length > 0 ? (
<button items.map((item, index) => (
key={item.id} <button
onClick={() => command(item)} key={item.id}
className={twMerge( onClick={() => command(item)}
"group flex w-full items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300", className={twMerge(
index === selectedIndex && "bg-light-200 dark:bg-dark-300", "group flex w-full items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300",
)} index === selectedIndex && "bg-light-200 dark:bg-dark-300",
> )}
<Avatar >
size="xs" <Avatar
name={item.label} size="xs"
imageUrl={ name={item.label}
item.image ? getAvatarUrl(item.image) : undefined imageUrl={item.image ? getAvatarUrl(item.image) : undefined}
} email={item.label}
email={item.label} />
/> <span className="ml-3 text-[12px] font-medium text-dark-900 dark:text-dark-1000">
<span className="ml-3 text-[12px] font-medium text-dark-900 dark:text-dark-1000"> {item.label}
{item.label} </span>
</span> </button>
</button> ))
)) : ( ) : (
<div className="flex items-center justify-start p-2"> <div className="flex items-center justify-start p-2">
<span className="text-dark-900 text-[12px] dark:text-dark-1000">No results</span> <span className="text-[12px] text-dark-900 dark:text-dark-1000">
No results
</span>
</div> </div>
)} )}
</div> </div>
@@ -436,6 +444,7 @@ export default function Editor({
{ {
extensions: [ extensions: [
StarterKit, StarterKit,
Markdown,
Placeholder.configure({ Placeholder.configure({
placeholder: readOnly placeholder: readOnly
? "" ? ""
@@ -462,35 +471,37 @@ export default function Editor({
}), }),
Mention.configure({ Mention.configure({
HTMLAttributes: { HTMLAttributes: {
class: 'mention', class: "mention",
}, },
suggestion: { suggestion: {
char: "@", char: "@",
items: ({ query }: { query: string }) => { items: ({ query }: { query: string }) => {
const all: MentionItem[] = workspaceMembers.map((member: WorkspaceMember) => ({ const all: MentionItem[] = workspaceMembers.map(
id: member.publicId, (member: WorkspaceMember) => ({
label: member?.user?.name ?? member.email, id: member.publicId,
image: member?.user?.image ?? null, label: member?.user?.name ?? member.email,
})); image: member?.user?.image ?? null,
}),
);
const q = query.toLowerCase(); const q = query.toLowerCase();
return all.filter((u) => u.label.toLowerCase().includes(q)); return all.filter((u) => u.label.toLowerCase().includes(q));
}, },
command: ({ editor, range, props }: any) => { command: ({ editor, range, props }: any) => {
const mentionHTML = `<span data-type="mention" data-id="${props.id}" data-label="${props.label}">@${props.label}</span>&nbsp;`; const mentionHTML = `<span data-type="mention" data-id="${props.id}" data-label="${props.label}">@${props.label}</span>&nbsp;`;
editor editor
.chain() .chain()
.focus() .focus()
.deleteRange(range) .deleteRange(range)
.insertContent(mentionHTML) .insertContent(mentionHTML)
.focus() .focus()
.run(); .run();
},
render: renderMentionSuggestions,
},
renderText({ options, node }) {
return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`;
}, },
render: renderMentionSuggestions,
},
renderText({ options, node }) {
return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`;
},
}), }),
], ],
content, content,

View File

@@ -4,6 +4,7 @@ import { z } from "zod";
import * as boardRepo from "@kan/db/repository/board.repo"; import * as boardRepo from "@kan/db/repository/board.repo";
import * as cardRepo from "@kan/db/repository/card.repo"; import * as cardRepo from "@kan/db/repository/card.repo";
import * as cardActivityRepo from "@kan/db/repository/cardActivity.repo"; import * as cardActivityRepo from "@kan/db/repository/cardActivity.repo";
import * as checklistRepo from "@kan/db/repository/checklist.repo";
import * as importRepo from "@kan/db/repository/import.repo"; import * as importRepo from "@kan/db/repository/import.repo";
import * as integrationsRepo from "@kan/db/repository/integration.repo"; import * as integrationsRepo from "@kan/db/repository/integration.repo";
import * as labelRepo from "@kan/db/repository/label.repo"; import * as labelRepo from "@kan/db/repository/label.repo";
@@ -22,6 +23,7 @@ export interface TrelloBoard {
labels: TrelloLabel[]; labels: TrelloLabel[];
lists: TrelloList[]; lists: TrelloList[];
cards: TrelloCard[]; cards: TrelloCard[];
checklists: TrelloChecklist[];
} }
interface TrelloLabel { interface TrelloLabel {
@@ -34,12 +36,34 @@ interface TrelloList {
name: string; name: string;
} }
interface TrelloChecklist {
id: string;
idCard: string;
name: string;
checkItems: TrelloCheckItem[];
}
interface TrelloCheckItem {
id: string;
name: string;
state: "complete" | "incomplete";
pos: number;
}
interface TrelloCard { interface TrelloCard {
id: string; id: string;
name: string; name: string;
desc: string; desc: string;
idList: string; idList: string;
labels: TrelloLabel[]; labels: TrelloLabel[];
idChecklists: string[];
checkItemStates: TrelloCheckItemState[];
}
interface TrelloCheckItemState {
idChecklist: string;
idCheckItem: string;
state: "complete" | "incomplete";
} }
export const importRouter = createTRPCRouter({ export const importRouter = createTRPCRouter({
@@ -170,7 +194,7 @@ export const importRouter = createTRPCRouter({
for (const boardId of input.boardIds) { for (const boardId of input.boardIds) {
const response = await fetch( const response = await fetch(
`${urls.trello}/boards/${boardId}?key=${apiKey}&token=${integration.accessToken}&lists=open&cards=open&labels=all`, `${urls.trello}/boards/${boardId}?key=${apiKey}&token=${integration.accessToken}&lists=open&cards=open&labels=all&checklists=all&checkItemStates=all`,
); );
const data = (await response.json()) as TrelloBoard; const data = (await response.json()) as TrelloBoard;
@@ -194,6 +218,18 @@ export const importRouter = createTRPCRouter({
sourceId: label.id, sourceId: label.id,
name: label.name, name: label.name,
})), })),
checklists: data.checklists
.filter((checklist) => checklist.idCard === _card.id)
.map((_checklist) => ({
sourceId: _checklist.id,
name: _checklist.name,
items: _checklist.checkItems.map((_item) => ({
sourceId: _item.id,
title: _item.name,
completed: _item.state === "complete",
index: _item.pos,
})),
})),
})), })),
})), })),
}; };
@@ -289,6 +325,97 @@ export const importRouter = createTRPCRouter({
await cardActivityRepo.bulkCreate(ctx.db, activities); await cardActivityRepo.bulkCreate(ctx.db, activities);
} }
const checklistsToCreate: {
cardId: number;
name: string;
createdBy: string;
index: number;
sourceId: string;
items: {
sourceId: string;
title: string;
completed: boolean;
index: number;
}[];
}[] = [];
for (const card of list.cards) {
const _card = createdCards.find(
(c) => c.sourceId === card.sourceId,
);
if (!_card || !card.checklists.length) continue;
for (
let checklistIndex = 0;
checklistIndex < card.checklists.length;
checklistIndex++
) {
const checklist = card.checklists[checklistIndex];
if (!checklist) continue;
checklistsToCreate.push({
cardId: _card.id,
name: checklist.name,
createdBy: userId,
index: checklistIndex,
sourceId: checklist.sourceId,
items: checklist.items.map((item) => ({
sourceId: item.sourceId,
title: item.title,
completed: item.completed,
index: item.index,
})),
});
}
}
if (checklistsToCreate.length > 0) {
const newChecklists = await checklistRepo.bulkCreate(
ctx.db,
checklistsToCreate.map((checklist) => ({
cardId: checklist.cardId,
name: checklist.name,
createdBy: checklist.createdBy,
index: checklist.index,
})),
);
const itemsToCreate: {
checklistId: number;
title: string;
createdBy: string;
index: number;
completed: boolean;
}[] = [];
for (let i = 0; i < checklistsToCreate.length; i++) {
const checklistData = checklistsToCreate[i];
const newChecklist = newChecklists[i];
if (!newChecklist || !checklistData?.items.length) continue;
// NOTE: Sorting here to prevent checklist items being out of order
const sortedItems = [...checklistData.items].sort(
(a, b) => a.index - b.index,
);
for (const item of sortedItems) {
itemsToCreate.push({
checklistId: newChecklist.id,
title: item.title,
createdBy: userId,
index: item.index,
completed: item.completed,
});
}
}
if (itemsToCreate.length > 0) {
await checklistRepo.bulkCreateItems(ctx.db, itemsToCreate);
}
}
if (createdLabels.length && createdCards.length) { if (createdLabels.length && createdCards.length) {
const cardLabelRelations: { const cardLabelRelations: {
cardId: number; cardId: number;

View File

@@ -46,6 +46,7 @@ export const createItem = async (
checklistId: number; checklistId: number;
title: string; title: string;
createdBy: string; createdBy: string;
completed?: boolean;
}, },
) => { ) => {
return db.transaction(async (tx) => { return db.transaction(async (tx) => {
@@ -65,7 +66,7 @@ export const createItem = async (
createdBy: checklistItemInput.createdBy, createdBy: checklistItemInput.createdBy,
checklistId: checklistItemInput.checklistId, checklistId: checklistItemInput.checklistId,
index: lastItem ? lastItem.index + 1 : 0, index: lastItem ? lastItem.index + 1 : 0,
completed: false, completed: checklistItemInput.completed ?? false,
}) })
.returning({ .returning({
id: checklistItems.id, id: checklistItems.id,
@@ -214,3 +215,129 @@ export const updateChecklistById = async (
.returning({ publicId: checklists.publicId, name: checklists.name }); .returning({ publicId: checklists.publicId, name: checklists.name });
return result; return result;
}; };
export const bulkCreate = async (
db: dbClient,
checklistInput: {
cardId: number;
name: string;
createdBy: string;
index: number;
}[],
) => {
if (checklistInput.length === 0) return [];
return db.transaction(async (tx) => {
const byCard = groupByKey(checklistInput, "cardId");
const allValuesToInsert: {
publicId: string;
cardId: number;
name: string;
createdBy: string;
index: number;
}[] = [];
for (const [cardId, items] of byCard.entries()) {
const last = await tx.query.checklists.findFirst({
columns: { index: true },
where: and(eq(checklists.cardId, cardId), isNull(checklists.deletedAt)),
orderBy: [desc(checklists.index)],
});
let nextIndex = last ? last.index + 1 : 0;
const sorted = [...items].sort((a, b) => a.index - b.index);
for (const item of sorted) {
allValuesToInsert.push({
publicId: generateUID(),
...item,
index: nextIndex++,
});
}
}
const inserted = await tx
.insert(checklists)
.values(allValuesToInsert)
.returning({ id: checklists.id, publicId: checklists.publicId });
return inserted;
});
};
export const bulkCreateItems = async (
db: dbClient,
checklistItemInput: {
checklistId: number;
title: string;
createdBy: string;
index: number;
completed: boolean;
}[],
) => {
if (checklistItemInput.length === 0) return [];
return db.transaction(async (tx) => {
const byChecklist = groupByKey(checklistItemInput, "checklistId");
const allValuesToInsert: {
publicId: string;
checklistId: number;
title: string;
createdBy: string;
index: number;
completed: boolean;
}[] = [];
for (const [checklistId, items] of byChecklist.entries()) {
const last = await tx.query.checklistItems.findFirst({
columns: { index: true },
where: and(
eq(checklistItems.checklistId, checklistId),
isNull(checklistItems.deletedAt),
),
orderBy: [desc(checklistItems.index)],
});
let nextIndex = last ? last.index + 1 : 0;
const sorted = [...items].sort((a, b) => a.index - b.index);
for (const item of sorted) {
allValuesToInsert.push({
publicId: generateUID(),
...item,
index: nextIndex++,
});
}
}
const inserted = await tx
.insert(checklistItems)
.values(allValuesToInsert)
.returning({
id: checklistItems.id,
publicId: checklistItems.publicId,
title: checklistItems.title,
completed: checklistItems.completed,
});
return inserted;
});
};
const groupByKey = <T extends Record<string, unknown>>(
items: T[],
keyField: keyof T,
): Map<number, T[]> => {
const grouped = new Map<number, T[]>();
for (const item of items) {
const key = item[keyField] as number;
const arr = grouped.get(key) ?? [];
arr.push(item);
grouped.set(key, arr);
}
return grouped;
};

41
pnpm-lock.yaml generated
View File

@@ -223,6 +223,9 @@ importers:
tippy.js: tippy.js:
specifier: ^6.3.7 specifier: ^6.3.7
version: 6.3.7 version: 6.3.7
tiptap-markdown:
specifier: ^0.8
version: 0.8.10(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))
zod: zod:
specifier: 'catalog:' specifier: 'catalog:'
version: 3.25.76 version: 3.25.76
@@ -3575,9 +3578,15 @@ packages:
'@types/katex@0.16.7': '@types/katex@0.16.7':
resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==}
'@types/linkify-it@3.0.5':
resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==}
'@types/linkify-it@5.0.0': '@types/linkify-it@5.0.0':
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
'@types/markdown-it@13.0.9':
resolution: {integrity: sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==}
'@types/markdown-it@14.1.2': '@types/markdown-it@14.1.2':
resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
@@ -3587,6 +3596,9 @@ packages:
'@types/mdast@4.0.4': '@types/mdast@4.0.4':
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
'@types/mdurl@1.0.5':
resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==}
'@types/mdurl@2.0.0': '@types/mdurl@2.0.0':
resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
@@ -5730,6 +5742,9 @@ packages:
make-error@1.3.6: make-error@1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
markdown-it-task-lists@2.1.1:
resolution: {integrity: sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==}
markdown-it@14.1.0: markdown-it@14.1.0:
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
hasBin: true hasBin: true
@@ -7411,6 +7426,11 @@ packages:
tippy.js@6.3.7: tippy.js@6.3.7:
resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==}
tiptap-markdown@0.8.10:
resolution: {integrity: sha512-iDVkR2BjAqkTDtFX0h94yVvE2AihCXlF0Q7RIXSJPRSR5I0PA1TMuAg6FHFpmqTn4tPxJ0by0CK7PUMlnFLGEQ==}
peerDependencies:
'@tiptap/core': ^2.0.3
title-case@2.1.1: title-case@2.1.1:
resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==} resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==}
@@ -11321,8 +11341,15 @@ snapshots:
'@types/katex@0.16.7': {} '@types/katex@0.16.7': {}
'@types/linkify-it@3.0.5': {}
'@types/linkify-it@5.0.0': {} '@types/linkify-it@5.0.0': {}
'@types/markdown-it@13.0.9':
dependencies:
'@types/linkify-it': 3.0.5
'@types/mdurl': 1.0.5
'@types/markdown-it@14.1.2': '@types/markdown-it@14.1.2':
dependencies: dependencies:
'@types/linkify-it': 5.0.0 '@types/linkify-it': 5.0.0
@@ -11336,6 +11363,8 @@ snapshots:
dependencies: dependencies:
'@types/unist': 3.0.3 '@types/unist': 3.0.3
'@types/mdurl@1.0.5': {}
'@types/mdurl@2.0.0': {} '@types/mdurl@2.0.0': {}
'@types/ms@2.1.0': {} '@types/ms@2.1.0': {}
@@ -13777,6 +13806,8 @@ snapshots:
make-error@1.3.6: {} make-error@1.3.6: {}
markdown-it-task-lists@2.1.1: {}
markdown-it@14.1.0: markdown-it@14.1.0:
dependencies: dependencies:
argparse: 2.0.1 argparse: 2.0.1
@@ -16085,6 +16116,14 @@ snapshots:
dependencies: dependencies:
'@popperjs/core': 2.11.8 '@popperjs/core': 2.11.8
tiptap-markdown@0.8.10(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)):
dependencies:
'@tiptap/core': 2.26.1(@tiptap/pm@2.26.1)
'@types/markdown-it': 13.0.9
markdown-it: 14.1.0
markdown-it-task-lists: 2.1.1
prosemirror-markdown: 1.13.2
title-case@2.1.1: title-case@2.1.1:
dependencies: dependencies:
no-case: 2.3.2 no-case: 2.3.2
@@ -16466,7 +16505,7 @@ snapshots:
'@webassemblyjs/wasm-parser': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1
acorn: 8.15.0 acorn: 8.15.0
acorn-import-phases: 1.0.4(acorn@8.15.0) acorn-import-phases: 1.0.4(acorn@8.15.0)
browserslist: 4.25.4 browserslist: 4.27.0
chrome-trace-event: 1.0.4 chrome-trace-event: 1.0.4
enhanced-resolve: 5.18.3 enhanced-resolve: 5.18.3
es-module-lexer: 1.7.0 es-module-lexer: 1.7.0