import useDatabase from "~/composables/useDatabase"; import { extname, basename } from 'node:path'; import type { File, FileType, Tag } from '~/types/api'; import { CanvasColor, CanvasContent } from "~/types/canvas"; import useMarkdown from "~/composables/useMarkdown"; const typeMapping: Record = { ".md": "Markdown", ".canvas": "Canvas" }; export default defineTask({ meta: { name: 'sync', description: 'Synchronise the project 1 with Obsidian', }, async run(event) { try { const tree = await $fetch('https://git.peaceultime.com/api/v1/repos/peaceultime/system-aspect/git/trees/master', { method: 'get', headers: { accept: 'application/json', }, params: { recursive: true, per_page: 1000, } }) as any; const files: File[] = await Promise.all(tree.tree.filter((e: any) => !e.path.startsWith(".")).map(async (e: any) => { if(e.type === 'tree') { const title = basename(e.path); const order = /(\d+)\. ?(.+)/gsmi.exec(title); const path = (e.path as string).split('/').map(f => { const check = /(\d+)\. ?(.+)/gsmi.exec(f); return check && check[2] ? check[2] : f }).join('/'); return { path: path.toLowerCase().replaceAll(" ", "-").normalize("NFD").replace(/[\u0300-\u036f]/g, ""), order: order && order[1] ? order[1] : 50, title: order && order[2] ? order[2] : title, type: 'Folder', content: null } } const extension = extname(e.path); const title = basename(e.path, extension); const order = /(\d+)\. ?(.+)/gsmi.exec(title); const path = (e.path as string).split('/').map(f => { const check = /(\d+)\. ?(.+)/gsmi.exec(f); return check && check[2] ? check[2] : f }).join('/'); const content = (await $fetch(`https://git.peaceultime.com/api/v1/repos/peaceultime/system-aspect/raw/${encodeURIComponent(e.path)}`)); return { path: (extension === '.md' ? path.replace(extension, '') : path).toLowerCase().replaceAll(" ", "-").normalize("NFD").replace(/[\u0300-\u036f]/g, ""), order: order && order[1] ? order[1] : 50, title: order && order[2] ? order[2] : title, type: (typeMapping[extension] ?? 'File'), content: reshapeContent(content as string, typeMapping[extension] ?? 'File') } })); let tags: Tag[] = []; const tagFile = files.find(e => e.path === "tags"); if(tagFile) { const parsed = useMarkdown()(tagFile.content); const titles = parsed.children.filter(e => e.type === 'element' && e.tagName.match(/h\d/)); for(let i = 0; i < titles.length; i++) { const start = titles[i].position?.start.offset ?? 0; const end = titles.length === i + 1 ? tagFile.content.length : titles[i + 1].position.start.offset - 1; tags.push({ tag: titles[i].properties.id, description: tagFile.content.substring(titles[i].position?.end.offset + 1, end) }); } } const db = useDatabase(); const oldFiles = db.prepare(`SELECT * FROM explorer_files WHERE project = ?1`).all('1') as File[]; const removeFiles = db.prepare(`DELETE FROM explorer_files WHERE project = ?1 AND path = ?2`); db.transaction((data: File[]) => data.forEach(e => removeFiles.run('1', e.path)))(oldFiles.filter(e => !files.find(f => f.path = e.path))); removeFiles.finalize(); const oldTags = db.prepare(`SELECT * FROM explorer_tags WHERE project = ?1`).all('1') as Tag[]; const removeTags = db.prepare(`DELETE FROM explorer_tags WHERE project = ?1 AND tag = ?2`); db.transaction((data: Tag[]) => data.forEach(e => removeTags.run('1', e.tag)))(oldTags.filter(e => !tags.find(f => f.tag = e.tag))); removeTags.finalize(); const insertFiles = db.prepare(`INSERT INTO explorer_files("project", "path", "owner", "title", "order", "type", "content") VALUES (1, $path, 1, $title, $order, $type, $content)`); const updateFiles = db.prepare(`UPDATE explorer_files SET content = $content WHERE project = 1 AND path = $path`); db.transaction((content) => { for (const item of content) { let order = item.order; if (typeof order === 'string') order = parseInt(item.order, 10); if (isNaN(order)) order = 999; if(oldFiles.find(e => item.path === e.path)) updateFiles.run({ $path: item.path, $content: item.content }); else insertFiles.run({ $path: item.path, $title: item.title, $type: item.type, $content: item.content, $order: order }); } })(files); insertFiles.finalize(); updateFiles.finalize(); const insertTags = db.prepare(`INSERT INTO explorer_tags("project", "tag", "description") VALUES (1, $tag, $description)`); const updateTags = db.prepare(`UPDATE explorer_tags SET description = $description WHERE project = 1 AND tag = $tag`); db.transaction((content) => { for (const item of content) { if (oldTags.find(e => item.tag === e.tag)) updateTags.run({ $tag: item.tag, $description: item.description }); else insertTags.run({ $tag: item.tag, $description: item.description }); } })(tags); insertTags.finalize(); updateTags.finalize(); useStorage('cache').clear(); console.log("Sync finished"); return { result: true }; } catch(e) { console.error(e); return { result: false }; } }, }) function reshapeContent(content: string, type: FileType): string | null { switch(type) { case "Markdown": case "File": return content; case "Canvas": const data = JSON.parse(content) as CanvasContent; data.edges.forEach(e => e.color = typeof e.color === 'string' ? getColor(e.color) : undefined); data.nodes.forEach(e => e.color = typeof e.color === 'string' ? getColor(e.color) : undefined); return JSON.stringify(data); default: case 'Folder': return null; } } function getColor(color: string): CanvasColor { const colors: Record = { '1': 'red', '2': 'orange', '3': 'yellow', '4': 'green', '5': 'cyan', '6': 'purple', }; if(colors.hasOwnProperty(color)) return { class: colors[color] }; else return { hex: color }; }