UNPKG

quiver-to-obsidian

Version:

Convert quiver libraray to obsidian library

246 lines 12.4 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import fse from 'fs-extra'; import * as path from 'path'; import ProgressBar from 'progress'; import ora from 'ora'; import TurndownService from 'turndown'; import { utimes } from 'utimes'; import { CellType, } from './type.js'; import { readLibrary, walkThroughNotebookHierarchty, readNoteContent } from './quiver_parse.js'; import { checkOutputDirPath, prepareDirectory, newDistinctNoteName } from './utils.js'; class Quiver { constructor(library, extNames) { this.newNotePathRecord = {}; this.outputQuiverPath = ''; this.noteCount = 0; this.library = library; if (extNames && extNames.length > 0) { this.needReplaceExtNames = extNames; } } static newQuiver(libraryPath, extNames) { return __awaiter(this, void 0, void 0, function* () { const spinner = ora('Loading library...').start(); const library = yield readLibrary(libraryPath); const quiver = new Quiver(library, extNames); spinner.stop(); return quiver; }); } transformQvLibraryToObsidian(outputPath) { return __awaiter(this, void 0, void 0, function* () { checkOutputDirPath(outputPath); // add `quiver` to output path this.outputQuiverPath = path.join(outputPath, 'quiver'); this.spinner = ora('Reading library...').start(); this.walkThroughNotebookHierarchty((notebook, parents) => { const newPathList = [this.outputQuiverPath]; parents.forEach((parentNotebook) => { newPathList.push(parentNotebook.meta.name); }); newPathList.push(notebook.meta.name); const newPath = path.join(...newPathList); // Prevent file name conflicts const noteNames = []; notebook.notes.forEach((note) => { if (this.newNotePathRecord[note.meta.uuid]) { throw new Error(`there has two notes with uuid(${note.meta.uuid}), please check and try again`); } let noteName = note.meta.title; if (noteNames.indexOf(noteName) > -1) { noteName = newDistinctNoteName(noteName, noteNames, 2); } noteNames.push(noteName); this.newNotePathRecord[note.meta.uuid] = path.join(newPath, `${noteName}.md`); }); }); this.noteCount = Object.keys(this.newNotePathRecord).length; this.bar = new ProgressBar('Processing [:bar] :current/:total', { total: this.noteCount }); yield this.writeLibrary(); return this.outputQuiverPath; }); } walkThroughNotebookHierarchty(callback) { var _a; const notebooks = {}; this.library.notebooks.forEach((notebook) => { notebooks[notebook.meta.uuid] = notebook; }); const parents = []; (_a = this.library.meta.children) === null || _a === void 0 ? void 0 : _a.forEach((meta) => { walkThroughNotebookHierarchty(meta, parents, (notebookName, parentNames) => { const parentNotebooks = []; parentNames.forEach((name) => { parentNotebooks.push(notebooks[name]); }); callback(notebooks[notebookName], parentNotebooks); }); }); } writeLibrary() { return __awaiter(this, void 0, void 0, function* () { const notebookInfoList = []; this.walkThroughNotebookHierarchty((notebook, parents) => { const newPathList = [this.outputQuiverPath]; parents.forEach((parentNotebook) => { newPathList.push(parentNotebook.meta.name); }); newPathList.push(notebook.meta.name); const newNotebookPath = path.join(...newPathList); notebookInfoList.push({ notebook, notebookPath: newNotebookPath }); }); yield Promise.all(notebookInfoList.map((n) => __awaiter(this, void 0, void 0, function* () { yield this.writeNotebook(n.notebook, n.notebookPath); }))); }); } writeNotebook(notebook, newNotebookPath) { return __awaiter(this, void 0, void 0, function* () { prepareDirectory(newNotebookPath); yield Promise.all(notebook.notes.map((note) => __awaiter(this, void 0, void 0, function* () { const notePath = this.newNotePathRecord[note.meta.uuid]; yield this.writeNote(note, notePath); }))); }); } writeNote(note, newNotePath) { var _a; return __awaiter(this, void 0, void 0, function* () { yield this.writeNoteToMarkdown(note, newNotePath); if (note.resources) { const resourceDirPath = path.join(this.outputQuiverPath, 'resources'); prepareDirectory(resourceDirPath); yield Promise.all(note.resources.files.map((file) => __awaiter(this, void 0, void 0, function* () { const fileName = this.replaceResourceName(file.name, true); const srcPath = path.join(note.notePath, 'resources', file.name); const dstPath = path.join(resourceDirPath, fileName); yield fse.copyFile(srcPath, dstPath); }))); } if (this.spinner) { this.spinner.stop(); this.spinner = undefined; } (_a = this.bar) === null || _a === void 0 ? void 0 : _a.tick(); }); } // transform note content to markdown and convert TextCell to markdown writeNoteToMarkdown(note, notePath) { return __awaiter(this, void 0, void 0, function* () { let fd; try { yield fse.createFile(notePath); fd = yield fse.promises.open(notePath, 'w+'); const noteContent = yield readNoteContent(note.contentPath); noteContent.cells.forEach((cell, i) => { var _a; if (i !== 0) { fd === null || fd === void 0 ? void 0 : fd.write('\n\n'); } const { data } = cell; switch (cell.type) { case CellType.MarkdownCell: { const transformData = this.transformQuiverResourceAndNoteLink(data); fd === null || fd === void 0 ? void 0 : fd.write(transformData); break; } case CellType.TextCell: { const turndownService = new TurndownService(); const markdown = turndownService.turndown(data); const transformData = this.transformQuiverResourceAndNoteLink(markdown); fd === null || fd === void 0 ? void 0 : fd.write(transformData); break; } case CellType.CodeCell: { const language = (_a = cell.language) !== null && _a !== void 0 ? _a : ''; fd === null || fd === void 0 ? void 0 : fd.write(`\`\`\`${language}\n${data}\n\`\`\``); break; } case CellType.LatexCell: { fd === null || fd === void 0 ? void 0 : fd.write(`\`\`\`latex\n${data}\n\`\`\``); break; } case CellType.DiagramCell: { let tool = 'Sequence diagram, see https://bramp.github.io/js-sequence-diagrams'; if (cell.diagramType === 'flow') { tool = 'Flowchart diagram, see http://flowchart.js.org'; } fd === null || fd === void 0 ? void 0 : fd.write(`\`\`\`javascript\n// ${tool}\n${data}\`\`\``); break; } default: break; } }); } catch (error) { throw error; } finally { if (fd) { fd.close(); } } try { // rewrite create time and update time of md file yield utimes(notePath, { btime: Number(note.meta.created_at * 1000), mtime: Number(note.meta.updated_at * 1000), atime: 0, }); } catch (error) { // ignore } }); } // transform quiver resource and note link url transformQuiverResourceAndNoteLink(data) { let transformData = data.replace(/quiver-image-url\//g, 'resources/'); transformData = transformData.replace(/quiver-file-url\//g, 'resources/'); transformData = this.replaceResourceName(transformData, false); // replace note link in content to obsidian link // eslint-disable-next-line max-len transformData = transformData.replace(/\[.*?\]\((quiver-note-url|quiver:\/\/\/notes)\/([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})\)/g, (_, __, uuid) => { const linkNotePath = this.newNotePathRecord[uuid]; const linkNoteName = path.basename(linkNotePath); return ` [[${linkNoteName}]]`; }); return transformData; } // rename resource file names and links replaceResourceName(data, isForFile) { const resourceTag = isForFile ? '' : 'resources/'; const prefix = isForFile ? '^' : '\\('; const suffix = isForFile ? '$' : '\\)'; // 1.remove url args from img file name, like: // `![](resources/47D5523597D28227C87950448B4780A5.jpg =344x387)` // `9FFEF50881EA1326EA55C1BC43EC9314.png&w=2048&q=75` // `55F20500B6E0C67E3EA78ED6C149B4D9.svg?style=social&label=Follow%20on%20Twitter` // eslint-disable-next-line max-len const clearSuffixReg = new RegExp(`${prefix}(${resourceTag}.*?\\.(bmp|jpg|png|tif|gif|pcx|tga|exif|fpx|svg|psd|cdr|pcd|dxf|ufo|eps|ai|raw|WMF|webp|jpeg|ico|awebp))(\\s|&|\\?).*${suffix}`, 'gi'); let transformData = data.replace(clearSuffixReg, (_, group1) => (isForFile ? group1 : `(${group1})`)); // replace unknown image file ext to `png` if (this.needReplaceExtNames && this.needReplaceExtNames.length > 0) { const replaceExt = this.needReplaceExtNames.join('|'); // eslint-disable-next-line max-len const renameAwebpReg = new RegExp(`${prefix}(${resourceTag}.*?)\\.(${replaceExt})${suffix}`, 'gi'); transformData = transformData.replace(renameAwebpReg, (_, group1) => (isForFile ? `${group1}.png` : `(${group1}.png)`)); } // add default ext (png) for none ext resource file like`(resources/BC8755B05A094564A25EA19E438B73B3)` // eslint-disable-next-line max-len const addDefaultExtReg = new RegExp(`${prefix}(${resourceTag}[0-9A-Z]{32})${suffix}`, 'g'); transformData = transformData.replace(addDefaultExtReg, (_, group1) => (isForFile ? `${group1}.png` : `(${group1}.png)`)); return transformData; } } export default Quiver; //# sourceMappingURL=index.js.map