conflict-nuxt-4/app/utils/tiptapMapper.js
2026-02-12 11:24:27 +03:30

364 lines
9.6 KiB
JavaScript
Executable File

// utils/tiptapMapper.js
export function tiptapToBlocks(tiptapJSON) {
const blocks = [];
function processNode(node, parentId = null) {
if (!node) return null;
const blockId = generateId();
switch (node.type) {
case "doc":
if (node.content) {
node.content.forEach((child) => processNode(child, null));
}
return null;
case "paragraph":
const paragraphBlock = {
id: blockId,
type: "paragraph",
content: extractTextContent(node),
parentId: parentId,
createdAt: new Date().toISOString(),
children: [],
};
blocks.push(paragraphBlock);
// پردازش فرزندان
if (node.content) {
node.content.forEach((child) => {
if (child.type !== "text") {
const childBlock = processNode(child, blockId);
if (childBlock) {
paragraphBlock.children.push(childBlock.id);
}
}
});
}
return paragraphBlock;
case "heading":
const headingBlock = {
id: blockId,
type: "heading",
level: node.attrs?.level || 1,
content: extractTextContent(node),
parentId: parentId,
createdAt: new Date().toISOString(),
children: [],
};
blocks.push(headingBlock);
// پردازش فرمت‌های درون عنوان
if (node.content) {
node.content.forEach((child) => {
if (child.type !== "text") {
const childBlock = processNode(child, blockId);
if (childBlock) {
headingBlock.children.push(childBlock.id);
}
}
});
}
return headingBlock;
case "bulletList":
case "orderedList":
const isOrdered = node.type === "orderedList";
const listBlock = {
id: blockId,
type: isOrdered ? "numbered_list" : "bulleted_list",
content: "",
parentId: parentId,
createdAt: new Date().toISOString(),
children: [],
};
blocks.push(listBlock);
// پردازش آیتم‌های لیست
if (node.content) {
node.content.forEach((listItem, index) => {
if (listItem.type === "listItem") {
const itemContent = extractTextContent(listItem);
const itemBlock = {
id: generateId(),
type: "list_item",
content: itemContent,
parentId: blockId,
number: isOrdered ? index + 1 : null,
createdAt: new Date().toISOString(),
children: [],
};
blocks.push(itemBlock);
listBlock.children.push(itemBlock.id);
// پردازش محتوای درون آیتم لیست
if (listItem.content) {
listItem.content.forEach((child) => {
if (child.type !== "paragraph" && child.type !== "text") {
const childBlock = processNode(child, itemBlock.id);
if (childBlock) {
itemBlock.children.push(childBlock.id);
}
}
});
}
}
});
}
return listBlock;
case "codeBlock":
const codeBlock = {
id: blockId,
type: "code",
language: node.attrs?.language || "javascript",
content: extractTextContent(node),
parentId: parentId,
createdAt: new Date().toISOString(),
children: [],
};
blocks.push(codeBlock);
return codeBlock;
case "blockquote":
const quoteBlock = {
id: blockId,
type: "quote",
content: extractTextContent(node),
parentId: parentId,
createdAt: new Date().toISOString(),
children: [],
};
blocks.push(quoteBlock);
return quoteBlock;
case "text":
// متن ساده را به عنوان یک بلوک متنی برمی‌گردانیم
const textBlock = {
id: blockId,
type: "text",
content: node.text || "",
marks: node.marks || [],
parentId: parentId,
createdAt: new Date().toISOString(),
children: [],
};
// فقط اگر درون یک بلوک دیگر نباشد، آن را به عنوان بلوک مستقل اضافه می‌کنیم
if (!parentId) {
blocks.push(textBlock);
}
return textBlock;
case "hardBreak":
const breakBlock = {
id: blockId,
type: "break",
content: "\n",
parentId: parentId,
createdAt: new Date().toISOString(),
children: [],
};
if (!parentId) {
blocks.push(breakBlock);
}
return breakBlock;
default:
// برای انواع ناشناخته
if (node.content) {
const unknownBlock = {
id: blockId,
type: "unknown",
content: extractTextContent(node),
originalType: node.type,
parentId: parentId,
createdAt: new Date().toISOString(),
children: [],
};
blocks.push(unknownBlock);
// بازگشتی پردازش فرزندان
node.content.forEach((child) => {
if (child.type !== "text") {
const childBlock = processNode(child, blockId);
if (childBlock) {
unknownBlock.children.push(childBlock.id);
}
}
});
return unknownBlock;
}
return null;
}
}
function extractTextContent(node) {
if (!node.content) return node.text || "";
let text = "";
node.content.forEach((child) => {
if (child.type === "text") {
text += child.text;
} else if (child.content || child.text) {
text += extractTextContent(child);
}
});
return text;
}
function generateId() {
return (
"block_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9)
);
}
// شروع پردازش
if (tiptapJSON && tiptapJSON.content) {
tiptapJSON.content.forEach((child) => processNode(child, null));
}
return blocks;
}
// تابع معکوس برای تست (اختیاری)
export function blocksToTiptap(blocks) {
const content = [];
const blockMap = new Map();
// ایجاد نقشه برای دسترسی سریع
blocks.forEach((block) => {
blockMap.set(block.id, block);
});
// تابع بازگشتی برای ایجاد گره‌ها
function createNode(block) {
switch (block.type) {
case "paragraph":
return {
type: "paragraph",
content: [
{
type: "text",
text: block.content || "",
},
],
};
case "heading":
return {
type: "heading",
attrs: { level: block.level || 1 },
content: [
{
type: "text",
text: block.content || "",
},
],
};
case "bulleted_list":
case "numbered_list":
const listItems = [];
if (block.children && block.children.length > 0) {
block.children.forEach((childId) => {
const childBlock = blockMap.get(childId);
if (childBlock && childBlock.type === "list_item") {
listItems.push({
type: "listItem",
content: [
{
type: "paragraph",
content: [
{
type: "text",
text: childBlock.content || "",
},
],
},
],
});
}
});
}
return {
type: block.type === "numbered_list" ? "orderedList" : "bulletList",
content: listItems,
};
case "code":
return {
type: "codeBlock",
attrs: { language: block.language || "javascript" },
content: [
{
type: "text",
text: block.content || "",
},
],
};
case "quote":
return {
type: "blockquote",
content: [
{
type: "paragraph",
content: [
{
type: "text",
text: block.content || "",
},
],
},
],
};
default:
return {
type: "paragraph",
content: [
{
type: "text",
text: block.content || "",
},
],
};
}
}
// فقط بلوک‌هایی که والد ندارند (بلوک‌های سطح بالا)
const topLevelBlocks = blocks.filter((block) => !block.parentId);
topLevelBlocks.forEach((block) => {
const node = createNode(block);
if (node) {
content.push(node);
}
});
return {
type: "doc",
content: content,
};
}