364 lines
9.6 KiB
JavaScript
Executable File
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,
|
|
};
|
|
}
|