import { Workspace, DocEntity, TodoEntity, Entity } from "./types";
import { OutputData } from "@editorjs/editorjs";
/**
* Convert EditorJS OutputData to Markdown
*/
export function docToMarkdown(content: OutputData): string {
return content.blocks
.map((block) => {
switch (block.type) {
case "header":
return `${"#".repeat((block.data as any).level)} ${(block.data as any).text}`;
case "paragraph":
return `${(block.data as any).text}\n`;
case "list":
if ((block.data as any).style === "ordered") {
return ((block.data as any).items as Array<{ content: string }>)
.map((item, i) => `${i + 1}. ${item.content}`)
.join("\n");
} else {
return ((block.data as any).items as Array<{ content: string }>)
.map((item) => `- ${item.content}`)
.join("\n");
}
case "code":
return `\`\`\`\n${(block.data as any).code}\n\`\`\``;
case "quote":
return `> ${(block.data as any).text}`;
case "delimiter":
return `---`;
case "image":
return `.file.url})`;
default:
return "";
}
})
.join("\n\n");
}
/**
* Convert EditorJS OutputData to HTML
*/
export function docToHtml(content: OutputData): string {
const htmlBlocks = content.blocks.map((block) => {
switch (block.type) {
case "header":
return `${(block.data as any).text}`;
case "paragraph":
return `
${(block.data as any).text}
`;
case "list":
const items = ((block.data as any).items as Array<{ content: string }>)
.map((item) => `${item.content}`)
.join("\n");
if ((block.data as any).style === "ordered") {
return `\n${items}\n
`;
} else {
return ``;
}
case "code":
return `${(block.data as any).code}
`;
case "quote":
return `${(block.data as any).text}
`;
case "delimiter":
return `
`;
case "image":
return `
`;
default:
return "";
}
});
return `
${htmlBlocks.join("\n")}
`;
}
/**
* Export a Doc entity as JSON
*/
export function exportDocAsJson(doc: DocEntity): string {
return JSON.stringify(
{
name: doc.name,
type: "doc",
content: doc.content,
createdAt: new Date(doc.createdAt).toISOString(),
updatedAt: new Date(doc.updatedAt).toISOString(),
},
null,
2,
);
}
/**
* Export a Doc entity as Markdown
*/
export function exportDocAsMarkdown(doc: DocEntity): string {
const header = `# ${doc.name}\n\nCreated: ${new Date(doc.createdAt).toISOString()}\nUpdated: ${new Date(doc.updatedAt).toISOString()}\n\n---\n\n`;
const content = docToMarkdown(doc.content);
return header + content;
}
/**
* Export a Doc entity as HTML
*/
export function exportDocAsHtml(doc: DocEntity): string {
const html = docToHtml(doc.content);
const headerHtml = `${doc.name}
Created: ${new Date(doc.createdAt).toISOString()}\nUpdated: ${new Date(doc.updatedAt).toISOString()}
`;
// Insert header before the first body element
return html
.replace("", `${doc.name}\n`)
.replace("", `\n${headerHtml}\n`);
}
/**
* Export a Todo entity as JSON
*/
export function exportTodoAsJson(todo: TodoEntity): string {
return JSON.stringify(
{
name: todo.name,
type: "todo",
config: todo.config,
tags: todo.tags,
tasks: todo.tasks,
createdAt: new Date(todo.createdAt).toISOString(),
updatedAt: new Date(todo.updatedAt).toISOString(),
},
null,
2,
);
}
/**
* Export a Todo entity as CSV
*/
export function exportTodoAsCsv(todo: TodoEntity): string {
const headers = [
"Title",
"Description",
"Status",
"Tags",
"Created",
"Updated",
];
const rows = Object.values(todo.tasks).map((task) => [
task.title,
task.description || "",
todo.config.statuses.find((s) => s.id === task.statusId)?.name || "",
task.tagIds.map((id) => todo.tags[id]?.name || "").join(";"),
new Date(task.createdAt).toISOString(),
new Date(task.updatedAt).toISOString(),
]);
const csvContent = [
headers.join(","),
...rows.map((row) =>
row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(","),
),
].join("\n");
return csvContent;
}
/**
* Export a Todo entity as Markdown
*/
export function exportTodoAsMarkdown(todo: TodoEntity): string {
let markdown = `# ${todo.name}\n\nCreated: ${new Date(todo.createdAt).toISOString()}\nUpdated: ${new Date(todo.updatedAt).toISOString()}\n\n`;
// Group tasks by status
const tasksByStatus: Record = {};
for (const status of todo.config.statuses) {
tasksByStatus[status.id] = {};
}
for (const [taskId, task] of Object.entries(todo.tasks)) {
const statusId = task.statusId;
if (!tasksByStatus[statusId]) {
tasksByStatus[statusId] = {};
}
tasksByStatus[statusId][taskId] = task;
}
// Add tasks by status
for (const status of todo.config.statuses) {
markdown += `## ${status.name}\n\n`;
const tasks = tasksByStatus[status.id];
if (Object.keys(tasks).length === 0) {
markdown += "- (No tasks)\n\n";
} else {
for (const task of Object.values(tasks)) {
const tags = task.tagIds
.map((id) => todo.tags[id]?.name || "")
.filter((n) => n)
.join(", ");
markdown += `- **${task.title}**${tags ? ` (${tags})` : ""}`;
if (task.description) {
markdown += `\n - ${task.description}`;
}
markdown += "\n";
}
}
markdown += "\n";
}
return markdown;
}
/**
* Export entire workspace as JSON
*/
export function exportWorkspaceAsJson(workspace: Workspace): string {
return JSON.stringify(workspace, null, 2);
}
/**
* Trigger download of a file
*/
export function downloadFile(
content: string,
filename: string,
mimeType: string = "text/plain",
): void {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
/**
* Export any entity based on its type
*/
export function exportEntity(
entity: Entity,
format: "json" | "markdown" | "html" | "csv",
): string {
if (entity.type === "doc") {
switch (format) {
case "json":
return exportDocAsJson(entity);
case "markdown":
return exportDocAsMarkdown(entity);
case "html":
return exportDocAsHtml(entity);
default:
return exportDocAsJson(entity);
}
} else if (entity.type === "todo") {
switch (format) {
case "json":
return exportTodoAsJson(entity);
case "markdown":
return exportTodoAsMarkdown(entity);
case "csv":
return exportTodoAsCsv(entity);
default:
return exportTodoAsJson(entity);
}
}
return "";
}