200 lines
5.1 KiB
TypeScript
200 lines
5.1 KiB
TypeScript
import { Workspace, DocEntity, TodoEntity } from "./types";
|
|
import { OutputData } from "@editorjs/editorjs";
|
|
|
|
/**
|
|
* Parse JSON file content back to Workspace
|
|
*/
|
|
export function importWorkspaceFromJson(jsonContent: string): Workspace {
|
|
try {
|
|
const parsed = JSON.parse(jsonContent);
|
|
|
|
// Basic validation
|
|
if (
|
|
typeof parsed !== "object" ||
|
|
parsed === null ||
|
|
typeof parsed.id !== "string" ||
|
|
typeof parsed.name !== "string"
|
|
) {
|
|
throw new Error("Invalid workspace JSON structure");
|
|
}
|
|
|
|
return parsed as Workspace;
|
|
} catch (err) {
|
|
throw new Error(`Failed to parse workspace JSON: ${err}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse JSON file content back to DocEntity
|
|
*/
|
|
export function importDocFromJson(jsonContent: string): DocEntity {
|
|
try {
|
|
const parsed = JSON.parse(jsonContent);
|
|
|
|
// Basic validation
|
|
if (
|
|
typeof parsed !== "object" ||
|
|
parsed === null ||
|
|
typeof parsed.name !== "string" ||
|
|
parsed.type !== "doc" ||
|
|
typeof parsed.content !== "object"
|
|
) {
|
|
throw new Error("Invalid doc JSON structure");
|
|
}
|
|
|
|
return parsed as DocEntity;
|
|
} catch (err) {
|
|
throw new Error(`Failed to parse doc JSON: ${err}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse JSON file content back to TodoEntity
|
|
*/
|
|
export function importTodoFromJson(jsonContent: string): TodoEntity {
|
|
try {
|
|
const parsed = JSON.parse(jsonContent);
|
|
|
|
// Basic validation
|
|
if (
|
|
typeof parsed !== "object" ||
|
|
parsed === null ||
|
|
typeof parsed.name !== "string" ||
|
|
parsed.type !== "todo" ||
|
|
typeof parsed.config !== "object"
|
|
) {
|
|
throw new Error("Invalid todo JSON structure");
|
|
}
|
|
|
|
return parsed as TodoEntity;
|
|
} catch (err) {
|
|
throw new Error(`Failed to parse todo JSON: ${err}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse CSV file content to extract tasks
|
|
*/
|
|
export function importTodoFromCsv(
|
|
csvContent: string,
|
|
todoEntity: TodoEntity,
|
|
): {
|
|
tasks: Record<string, any>;
|
|
errors: string[];
|
|
} {
|
|
const lines = csvContent.trim().split("\n");
|
|
const tasks: Record<string, any> = {};
|
|
const errors: string[] = [];
|
|
|
|
if (lines.length < 2) {
|
|
errors.push("CSV file must have at least a header row");
|
|
return { tasks, errors };
|
|
}
|
|
|
|
const headers = lines[0].split(",").map((h) => h.trim().toLowerCase());
|
|
const titleIndex = headers.indexOf("title");
|
|
const descriptionIndex = headers.indexOf("description");
|
|
const statusIndex = headers.indexOf("status");
|
|
const tagsIndex = headers.indexOf("tags");
|
|
const docIndex = headers.indexOf("doc");
|
|
|
|
if (titleIndex === -1) {
|
|
errors.push("CSV must have a 'Title' column");
|
|
return { tasks, errors };
|
|
}
|
|
|
|
for (let i = 1; i < lines.length; i++) {
|
|
try {
|
|
const line = lines[i];
|
|
if (!line.trim()) continue;
|
|
|
|
const values = line.split(",").map((v) => v.trim().replace(/^"|"$/g, ""));
|
|
|
|
const title = values[titleIndex];
|
|
if (!title) {
|
|
errors.push(`Row ${i + 1}: Missing title`);
|
|
continue;
|
|
}
|
|
|
|
// Find status by name
|
|
let statusId = todoEntity.config.statuses[0].id;
|
|
if (statusIndex !== -1 && values[statusIndex]) {
|
|
const status = todoEntity.config.statuses.find(
|
|
(s) => s.name.toLowerCase() === values[statusIndex].toLowerCase(),
|
|
);
|
|
if (status) {
|
|
statusId = status.id;
|
|
}
|
|
}
|
|
|
|
// Parse tags
|
|
let tagIds: string[] = [];
|
|
if (tagsIndex !== -1 && values[tagsIndex]) {
|
|
const tagNames = values[tagsIndex].split(";").map((t) => t.trim());
|
|
tagIds = tagNames
|
|
.map((name) => {
|
|
const tag = Object.entries(todoEntity.tags).find(
|
|
([, t]) => t.name.toLowerCase() === name.toLowerCase(),
|
|
);
|
|
return tag ? tag[0] : null;
|
|
})
|
|
.filter((id) => id !== null) as string[];
|
|
}
|
|
|
|
const taskId = `task_${Math.random().toString(36).slice(2, 9)}`;
|
|
tasks[taskId] = {
|
|
id: taskId,
|
|
title,
|
|
description: descriptionIndex !== -1 ? values[descriptionIndex] : "",
|
|
doc:
|
|
docIndex !== -1 && values[docIndex]
|
|
? JSON.parse(values[docIndex])
|
|
: undefined,
|
|
statusId,
|
|
tagIds,
|
|
createdAt: Date.now(),
|
|
updatedAt: Date.now(),
|
|
};
|
|
} catch (err) {
|
|
errors.push(`Row ${i + 1}: ${err}`);
|
|
}
|
|
}
|
|
|
|
return { tasks, errors };
|
|
}
|
|
|
|
/**
|
|
* Open file picker and read file content
|
|
*/
|
|
export function openFileDialog(
|
|
accept: string = ".json",
|
|
): Promise<{ content: string; filename: string }> {
|
|
return new Promise((resolve, reject) => {
|
|
const input = document.createElement("input");
|
|
input.type = "file";
|
|
input.accept = accept;
|
|
|
|
input.onchange = (e: any) => {
|
|
const file = e.target.files?.[0];
|
|
if (!file) {
|
|
reject(new Error("No file selected"));
|
|
return;
|
|
}
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = (event: any) => {
|
|
try {
|
|
const content = event.target?.result as string;
|
|
resolve({ content, filename: file.name });
|
|
} catch (err) {
|
|
reject(err);
|
|
}
|
|
};
|
|
reader.onerror = () => reject(new Error("Failed to read file"));
|
|
reader.readAsText(file);
|
|
};
|
|
|
|
input.click();
|
|
});
|
|
}
|