mirror of
https://github.com/jia6y/flomo-to-obsidian.git
synced 2026-07-22 07:40:28 +00:00
1. Added: merge by dae
2. Code improvement
This commit is contained in:
parent
2139db2120
commit
ae8bfc58a4
10 changed files with 101 additions and 25420 deletions
48
lib/flomo.ts
48
lib/flomo.ts
|
|
@ -1,39 +1,45 @@
|
|||
import { parse, HTMLElement } from 'node-html-parser';
|
||||
import { NodeHtmlMarkdown } from 'node-html-markdown';
|
||||
export class Flomo {
|
||||
private memoNodes: Array<HTMLElement>;
|
||||
private tagNodes: Array<HTMLElement>;
|
||||
stat: Record<string, number>
|
||||
memos: Record<string, string>[];
|
||||
tags: string[];
|
||||
files: Record<string, string[]>;
|
||||
|
||||
constructor(flomoData: string) {
|
||||
const root = parse(flomoData);
|
||||
this.memoNodes = root.querySelectorAll(".memo");
|
||||
this.tagNodes = root.getElementById("tag").querySelectorAll("option");
|
||||
this.stat = { "memo": this.memoNodes.length, "tag": this.tagNodes.length }
|
||||
this.memos = this.loadMemos(root.querySelectorAll(".memo"));
|
||||
this.tags = this.loadTags(root.getElementById("tag").querySelectorAll("option"));
|
||||
this.files = {};
|
||||
}
|
||||
|
||||
memos(): Record<string, string>[] {
|
||||
|
||||
private loadMemos(memoNodes: Array<HTMLElement>): Record<string, string>[] {
|
||||
const res: Record<string, string>[] = [];
|
||||
this.memoNodes.forEach(i => {
|
||||
res.push({"title": (this.extrtactTitle(i.querySelector(".time").textContent)) as string,
|
||||
"date": (i.querySelector(".time").textContent.split(" ")[0]) as string,
|
||||
"content": `Created at: ${this.extractContent(i.innerHTML)} \n\n`})
|
||||
const extrtactTitle = (item: string) => { return item.replace(/(-|:|\s)/gi, "_") }
|
||||
const extractContent = (content: string) => {
|
||||
return NodeHtmlMarkdown.translate(content).replace('\[','[').replace('\]',']')
|
||||
}
|
||||
|
||||
memoNodes.forEach(i => {
|
||||
|
||||
const dateTime = i.querySelector(".time").textContent;
|
||||
const title = extrtactTitle(dateTime);
|
||||
const content = extractContent(i.querySelector(".content").innerHTML) + "\n" +
|
||||
extractContent(i.querySelector(".files").innerHTML);
|
||||
|
||||
res.push({
|
||||
"title": title,
|
||||
"date": dateTime.split(" ")[0],
|
||||
"content": "`" + dateTime + "`\n" + content,
|
||||
})
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
tags(): string[] {
|
||||
private loadTags(tagNodes: Array<HTMLElement>): string[] {
|
||||
const res: string[] = [];
|
||||
this.tagNodes.slice(1).forEach(i => { res.push(i.textContent); })
|
||||
tagNodes.slice(1).forEach( i => { res.push(i.textContent); })
|
||||
return res;
|
||||
}
|
||||
|
||||
private extrtactTitle(item: string): string {
|
||||
return item.replace(/(-|:|\s)/gi, "_")
|
||||
}
|
||||
|
||||
private extractContent(content: string): string {
|
||||
return NodeHtmlMarkdown.translate(content).replace('\[','[').replace('\]',']')
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ const FLOMO_CACHE_LOC = path.join(os.homedir(), ".flomo/cache/");
|
|||
|
||||
|
||||
export class FlomoImporter {
|
||||
private config: Record<string, string>;
|
||||
private config: Record<string, any>;
|
||||
private app: App;
|
||||
|
||||
constructor(app: App, config: Record<string, string>) {
|
||||
|
|
@ -29,27 +29,44 @@ export class FlomoImporter {
|
|||
return parse5.serialize(document);
|
||||
}
|
||||
|
||||
private async importMemos(flomo: Flomo): Promise<void> {
|
||||
for (const [idx, memo] of flomo.memos().entries()) {
|
||||
private async importMemos(flomo: Flomo): Promise<Flomo> {
|
||||
const allowBilink: boolean = this.config["expOptionAllowbilink"];
|
||||
const margeByDate: boolean = this.config["mergeByDate"];
|
||||
|
||||
for (const [idx, memo] of flomo.memos.entries()) {
|
||||
|
||||
const memoSubDir = `${this.config["flomoTarget"]}/${this.config["memoTarget"]}/${memo["date"]}`;
|
||||
const memoFilePath = `${memoSubDir}/memo@${memo["title"]}_${flomo.stat["memo"] - idx}.md`;
|
||||
const memoFilePath = margeByDate ? `${memoSubDir}/memo@${memo["date"]}.md` : `${memoSubDir}/memo@${memo["title"]}_${flomo.memos.length - idx}.md`;
|
||||
|
||||
await fs.mkdirp(`${this.config["baseDir"]}/${memoSubDir}`);
|
||||
|
||||
const content = (() => {
|
||||
const res = memo["content"].replace(/!\[\]\(file\//gi, ";
|
||||
if (this.config["expOptionAllowbilink"] == true) {
|
||||
|
||||
if (allowBilink == true) {
|
||||
return res.replace(`\\[\\[`, "[[").replace(`\\]\\]`, "]]")
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
})();
|
||||
|
||||
if (!(memoFilePath in flomo.files)) {
|
||||
flomo.files[memoFilePath] = []
|
||||
}
|
||||
|
||||
flomo.files[memoFilePath].push(content);
|
||||
}
|
||||
|
||||
console.log(flomo.files);
|
||||
|
||||
for (const filePath in flomo.files) {
|
||||
await this.app.vault.adapter.write(
|
||||
`${memoFilePath}`,
|
||||
content
|
||||
filePath,
|
||||
flomo.files[filePath].join("\n\n")
|
||||
);
|
||||
}
|
||||
|
||||
return flomo;
|
||||
}
|
||||
|
||||
async import(): Promise<Flomo> {
|
||||
|
|
@ -79,7 +96,7 @@ export class FlomoImporter {
|
|||
const backupData = await this.sanitize(`${tmpDir}/${files[0].path}/index.html`)
|
||||
const flomo = new Flomo(backupData);
|
||||
|
||||
await this.importMemos(flomo)
|
||||
const memos = await this.importMemos(flomo)
|
||||
|
||||
|
||||
// 5. Ob Intergations
|
||||
|
|
@ -90,7 +107,7 @@ export class FlomoImporter {
|
|||
|
||||
// If Generate Canvas
|
||||
if (this.config["optionsCanvas"] != "skip") {
|
||||
await generateCanvas(app, flomo, this.config);
|
||||
await generateCanvas(app, memos, this.config);
|
||||
}
|
||||
|
||||
// 6. Cleanup Workspace
|
||||
|
|
|
|||
|
|
@ -13,30 +13,26 @@ const canvasSize = {
|
|||
"S": [230, 280]
|
||||
}
|
||||
|
||||
export async function generateCanvas(app: App, flomo: Flomo, config: Record<string, string>): Promise<void> {
|
||||
const size: number[] = canvasSize[config["canvasSize"]];
|
||||
if (flomo.stat["memo"] > 0) {
|
||||
export async function generateCanvas(app: App, flomo: Flomo, config: Record<string, any>): Promise<void> {
|
||||
if (flomo.memos.length > 0) {
|
||||
const size: number[] = canvasSize[config["canvasSize"]];
|
||||
const buffer: Record<string, string>[] = [];
|
||||
const canvas_file = `${config["flomoTarget"]}/Flomo Canvas.canvas`;
|
||||
const canvasFile = `${config["flomoTarget"]}/Flomo Canvas.canvas`;
|
||||
const memoFiles = Object.keys(flomo.files);
|
||||
|
||||
for (const [idx, memo] of flomo.memos().entries()) {
|
||||
for (const [idx, memoFile] of memoFiles.entries()) {
|
||||
|
||||
const _id: string = uuidv4();
|
||||
const _x: number = (idx % 8) * (size[0] + 20); // margin: 20px, length: 8n
|
||||
const _y: number = (Math.floor(idx / 8)) * (size[1] + 20); // margin: 20px
|
||||
|
||||
const content = (() => {
|
||||
const res = memo["content"].replace(/!\[\]\(file\//gi, ";
|
||||
if (config["expOptionAllowbilink"] == true) {
|
||||
return res.replace(`\\[\\[`, "[[").replace(`\\]\\]`, "]]")
|
||||
}
|
||||
return res;
|
||||
})();
|
||||
const content = flomo.files[memoFile];
|
||||
|
||||
const canvasNode: Record<string, any> = (() => {
|
||||
if (config["optionsCanvas"] == "copy_with_link") {
|
||||
return {
|
||||
"type": "file",
|
||||
"file": `${config["flomoTarget"]}/${config["memoTarget"]}/${memo["date"].split(" ")[0]}/memo@${memo["title"]}_${flomo.stat["memo"] - idx}.md`,
|
||||
"file": memoFile,
|
||||
"id": _id,
|
||||
"x": _x,
|
||||
"y": _y,
|
||||
|
|
@ -46,7 +42,7 @@ export async function generateCanvas(app: App, flomo: Flomo, config: Record<stri
|
|||
} else {
|
||||
return {
|
||||
"type": "text",
|
||||
"text": content,
|
||||
"text": content.join("\n\n"),
|
||||
"id": _id,
|
||||
"x": _x,
|
||||
"y": _y,
|
||||
|
|
@ -60,6 +56,7 @@ export async function generateCanvas(app: App, flomo: Flomo, config: Record<stri
|
|||
};
|
||||
|
||||
const canvasJson = { "nodes": buffer, "edges": [] }
|
||||
await app.vault.adapter.write(canvas_file, JSON.stringify(canvasJson));
|
||||
await app.vault.adapter.write(canvasFile, JSON.stringify(canvasJson));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -2,20 +2,21 @@ import { App } from 'obsidian';
|
|||
import { Flomo } from '../flomo';
|
||||
|
||||
|
||||
export async function generateMoments(app: App, flomo: Flomo, config: Record<string, string>): Promise<void> {
|
||||
if (flomo.stat["memo"] > 0) {
|
||||
export async function generateMoments(app: App, flomo: Flomo, config: Record<string, any>): Promise<void> {
|
||||
if (flomo.memos.length > 0) {
|
||||
const buffer: string[] = [];
|
||||
const tags: string[] = [];
|
||||
const index_file = `${config["flomoTarget"]}/Flomo Moments.md`;
|
||||
const memoFiles = Object.keys(flomo.files);
|
||||
|
||||
buffer.push(`updated at: ${(new Date()).toLocaleString()}\n\n`);
|
||||
|
||||
for (const tag of flomo.tags()) { tags.push(`#${tag}`);};
|
||||
for (const tag of flomo.tags) { tags.push(`#${tag}`);};
|
||||
|
||||
buffer.push(tags.join(' ') + "\n\n---\n\n");
|
||||
|
||||
for (const [idx, memo] of flomo.memos().entries()) {
|
||||
buffer.push(`![[${config["memoTarget"]}/${memo["date"].split(" ")[0]}/memo@${memo["title"]}_${flomo.stat["memo"] - idx}]]\n\n---\n\n`);
|
||||
for (const [idx, memoFile] of memoFiles.entries()) {
|
||||
buffer.push(`![[${memoFile}]]\n\n---\n\n`);
|
||||
};
|
||||
|
||||
await app.vault.adapter.write(index_file, buffer.join("\n"));
|
||||
|
|
|
|||
24
lib/ui.ts
24
lib/ui.ts
|
|
@ -28,7 +28,7 @@ export class ImporterUI extends Modal {
|
|||
|
||||
const flomo = await (new FlomoImporter(this.app, config)).import();
|
||||
|
||||
new Notice(`🎉 Import Completed.\nTotal: ${flomo.stat["memo"].toString()} memos`)
|
||||
new Notice(`🎉 Import Completed.\nTotal: ${flomo.memos.length} memos`)
|
||||
this.rawPath = "";
|
||||
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ export class ImporterUI extends Modal {
|
|||
}));
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Memos Location')
|
||||
.setName('Memo Location')
|
||||
.setDesc('set the location to store memos (under flomo root)')
|
||||
.addText((text) => text
|
||||
.setPlaceholder('memos')
|
||||
|
|
@ -75,8 +75,8 @@ export class ImporterUI extends Modal {
|
|||
}));
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Moments Options')
|
||||
.setDesc('set moments options')
|
||||
.setName('Memo Options')
|
||||
.setDesc('set memo options')
|
||||
.addDropdown((drp) => {
|
||||
drp.addOption("copy_with_link", "Generate Moments")
|
||||
.addOption("skip", "Skip Moments")
|
||||
|
|
@ -86,12 +86,24 @@ export class ImporterUI extends Modal {
|
|||
})
|
||||
})
|
||||
|
||||
const momentOptionBlock: HTMLDivElement = contentEl.createEl("div", { cls: "canvasOptionBlock" });
|
||||
const momentOptionLabel: HTMLLabelElement = momentOptionBlock.createEl("label");
|
||||
const mergeByDate: HTMLInputElement = momentOptionLabel.createEl("input", { type: "checkbox", cls: "ckbox" })
|
||||
mergeByDate.checked = this.plugin.settings.mergeByDate;
|
||||
mergeByDate.onchange = (ev) => {
|
||||
this.plugin.settings.mergeByDate = ev.currentTarget.checked;
|
||||
};
|
||||
|
||||
momentOptionLabel.createEl("small", { text: "merge memos by date" });
|
||||
|
||||
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Canvas Options')
|
||||
.setDesc('set canvas options')
|
||||
.addDropdown((drp) => {
|
||||
drp.addOption("copy_with_link", "Generate Canvas")
|
||||
.addOption("copy_with_content", "Generate Canvas with content")
|
||||
.addOption("copy_with_content", "Generate Canvas (with content)")
|
||||
.addOption("skip", "Skip Canvas")
|
||||
.setValue(this.plugin.settings.optionsCanvas)
|
||||
.onChange(async (value) => {
|
||||
|
|
@ -150,7 +162,7 @@ export class ImporterUI extends Modal {
|
|||
this.plugin.settings.expOptionAllowbilink = ev.currentTarget.checked;
|
||||
};
|
||||
|
||||
expOptionLabel.createEl("small", { text: "Convert bidirectonal link: [[link]]" });
|
||||
expOptionLabel.createEl("small", { text: "Convert bidirectonal link. example: [[abc]]" });
|
||||
|
||||
|
||||
new Setting(contentEl)
|
||||
|
|
|
|||
25356
main.js
25356
main.js
File diff suppressed because one or more lines are too long
8
main.ts
8
main.ts
|
|
@ -8,16 +8,18 @@ interface MyPluginSettings {
|
|||
optionsMoments: string,
|
||||
optionsCanvas: string,
|
||||
expOptionAllowbilink: boolean,
|
||||
canvasSize: string
|
||||
canvasSize: string,
|
||||
mergeByDate: boolean
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: MyPluginSettings = {
|
||||
flomoTarget: 'flomo',
|
||||
memoTarget: 'memos',
|
||||
optionsMoments: "copy_with_link",
|
||||
optionsCanvas: "copy_with_link",
|
||||
optionsCanvas: "copy_with_content",
|
||||
expOptionAllowbilink: true,
|
||||
canvasSize: 'M'
|
||||
canvasSize: 'M',
|
||||
mergeByDate: false
|
||||
}
|
||||
|
||||
export default class FlomoImporterPlugin extends Plugin {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "flomo-importer",
|
||||
"name": "Flomo Importer",
|
||||
"version": "1.1.1",
|
||||
"version": "1.1.2",
|
||||
"minAppVersion": "1.1.16",
|
||||
"description": "Make Flomo Memos to Obsidian Notes",
|
||||
"author": "Jialu Y",
|
||||
|
|
|
|||
|
|
@ -12,12 +12,13 @@ If your plugin does not need CSS, delete this file.
|
|||
margin: 0 0 0px;
|
||||
width: 100%;
|
||||
display: block;
|
||||
background: #f5f5f5;
|
||||
background: transparent
|
||||
}
|
||||
|
||||
.uploadbox:hover {
|
||||
background: #e0e0e0;
|
||||
/*background: #e0e0e0;*/
|
||||
cursor: pointer;
|
||||
border: 1px dashed #915109;
|
||||
}
|
||||
|
||||
.expOptionBlock {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"1.0.0": "0.15.0",
|
||||
"1.1.0": "1.1.16",
|
||||
"1.1.1": "1.1.16"
|
||||
"1.1.1": "1.1.16",
|
||||
"1.1.2": "1.1.16"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue