Skip to content

Commit

Permalink
Merge pull request #6 from davisriedel/master
Browse files Browse the repository at this point in the history
Add API for use in other plugins or scripts
  • Loading branch information
mgmeyers authored Feb 20, 2024
2 parents 81067e2 + 71f2914 commit e0e86e8
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 107 deletions.
109 changes: 2 additions & 107 deletions src/BakeModal.ts
Original file line number Diff line number Diff line change
@@ -1,120 +1,15 @@
import {
App,
FileSystemAdapter,
Modal,
Platform,
Setting,
TFile,
parseLinktext,
resolveSubpath,
} from 'obsidian';

import EasyBake, { BakeSettings } from './main';
import EasyBake from './main';
import {
applyIndent,
extractSubpath,
getWordCount,
sanitizeBakedContent,
stripFirstBullet,
} from './util';

const lineStartRE = /(?:^|\n) *$/;
const listLineStartRE = /(?:^|\n)([ \t]*)(?:[-*+]|[0-9]+[.)]) +$/;
const lineEndRE = /^ *(?:\r?\n|$)/;

async function bake(
app: App,
file: TFile,
subpath: string | null,
ancestors: Set<TFile>,
settings: BakeSettings
) {
const { vault, metadataCache } = app;

let text = await vault.cachedRead(file);
const cache = metadataCache.getFileCache(file);

// No cache? Return the file as is...
if (!cache) return text;

// Get the target block or section if we have a subpath
const resolvedSubpath = subpath ? resolveSubpath(cache, subpath) : null;
if (resolvedSubpath) {
text = extractSubpath(text, resolvedSubpath, cache);
}

const links = settings.bakeLinks ? cache.links || [] : [];
const embeds = settings.bakeEmbeds ? cache.embeds || [] : [];
const targets = [...links, ...embeds];

// No links in the current file; we can stop here...
if (targets.length === 0) return text;

targets.sort((a, b) => a.position.start.offset - b.position.start.offset);

const newAncestors = new Set(ancestors);
newAncestors.add(file);

// This helps us keep track of edits we make to the text and sync them with
// position data held in the metadata cache
let posOffset = 0;
for (const target of targets) {
const { path, subpath } = parseLinktext(target.link);
const linkedFile = metadataCache.getFirstLinkpathDest(path, file.path);

if (!linkedFile) continue;

const start = target.position.start.offset + posOffset;
const end = target.position.end.offset + posOffset;
const prevLen = end - start;

const before = text.substring(0, start);
const after = text.substring(end);

const listMatch = settings.bakeInList
? before.match(listLineStartRE)
: null;
const isInline =
!(listMatch || lineStartRE.test(before)) || !lineEndRE.test(after);
const isMarkdownFile = linkedFile.extension === 'md';

const replaceTarget = (replacement: string) => {
text = before + replacement + after;
posOffset += replacement.length - prevLen;
};

if (!isMarkdownFile) {
// Skip link processing if we're not converting file links...
if (!settings.convertFileLinks) continue;

const adapter = app.vault.adapter as FileSystemAdapter;

// FYI: The mobile adapter also has getFullPath so this should work on mobile and desktop
// The mobile adapter isn't exported in the public API, however
if (!adapter.getFullPath) continue;
const fullPath = adapter.getFullPath(linkedFile.path);
const protocol = Platform.isWin ? 'file:///' : 'file://';
replaceTarget(`![](${protocol}${encodeURI(fullPath)})`);
continue;
}

// Replace the link with its text if the it's inline or would create an infinite loop
if (newAncestors.has(linkedFile) || isInline) {
replaceTarget(target.displayText || path);
continue;
}

// Recurse and bake the linked file...
const baked = sanitizeBakedContent(
await bake(app, linkedFile, subpath, newAncestors, settings)
);
replaceTarget(
listMatch ? applyIndent(stripFirstBullet(baked), listMatch[1]) : baked
);
}

return text;
}
import { bake } from "./bake";

function disableBtn(btn: HTMLButtonElement) {
btn.removeClass('mod-cta');
Expand Down
60 changes: 60 additions & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { bake as bakeUtil } from "./bake";
import {
TFile,
} from 'obsidian';
import EasyBake, { BakeSettings } from './main';

export class EasyBakeApi {
private plugin: EasyBake;

constructor(plugin: EasyBake) {
this.plugin = plugin;
}

public async bakeToString(
inputPath: string,
settings: BakeSettings
) {
const app = this.plugin.app;
const file = app.vault.getAbstractFileByPath(inputPath);

if (!(file instanceof TFile)) {
console.error("Input file does not exist");
return;
}

return await bakeUtil(app, file, null, new Set(), settings);
}

public async bakeToFile(
inputPath: string,
outputPath: string,
settings: BakeSettings
) {
const baked = await this.bakeToString(inputPath, settings);
if (!baked) return;

const app = this.plugin.app;
let existing = app.vault.getAbstractFileByPath(outputPath);
if (existing instanceof TFile) {
await app.vault.modify(existing, baked);
} else {
existing = await app.vault.create(outputPath, baked);
}
}

public async bakeAndOpen(
inputPath: string,
outputPath: string,
settings: BakeSettings
) {
await this.bakeToFile(inputPath, outputPath, settings);

const app = this.plugin.app;
let existing = app.vault.getAbstractFileByPath(outputPath);
if (existing instanceof TFile) {
await app.workspace.getLeaf('tab').openFile(existing);
}
}
}

114 changes: 114 additions & 0 deletions src/bake.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import {
App,
FileSystemAdapter,
Platform,
TFile,
parseLinktext,
resolveSubpath,
} from 'obsidian';

import { BakeSettings } from './main';
import {
applyIndent,
extractSubpath,
sanitizeBakedContent,
stripFirstBullet,
} from './util';

const lineStartRE = /(?:^|\n) *$/;
const listLineStartRE = /(?:^|\n)([ \t]*)(?:[-*+]|[0-9]+[.)]) +$/;
const lineEndRE = /^ *(?:\r?\n|$)/;

export async function bake(
app: App,
file: TFile,
subpath: string | null,
ancestors: Set<TFile>,
settings: BakeSettings
) {
const { vault, metadataCache } = app;

let text = await vault.cachedRead(file);
const cache = metadataCache.getFileCache(file);

// No cache? Return the file as is...
if (!cache) return text;

// Get the target block or section if we have a subpath
const resolvedSubpath = subpath ? resolveSubpath(cache, subpath) : null;
if (resolvedSubpath) {
text = extractSubpath(text, resolvedSubpath, cache);
}

const links = settings.bakeLinks ? cache.links || [] : [];
const embeds = settings.bakeEmbeds ? cache.embeds || [] : [];
const targets = [...links, ...embeds];

// No links in the current file; we can stop here...
if (targets.length === 0) return text;

targets.sort((a, b) => a.position.start.offset - b.position.start.offset);

const newAncestors = new Set(ancestors);
newAncestors.add(file);

// This helps us keep track of edits we make to the text and sync them with
// position data held in the metadata cache
let posOffset = 0;
for (const target of targets) {
const { path, subpath } = parseLinktext(target.link);
const linkedFile = metadataCache.getFirstLinkpathDest(path, file.path);

if (!linkedFile) continue;

const start = target.position.start.offset + posOffset;
const end = target.position.end.offset + posOffset;
const prevLen = end - start;

const before = text.substring(0, start);
const after = text.substring(end);

const listMatch = settings.bakeInList
? before.match(listLineStartRE)
: null;
const isInline =
!(listMatch || lineStartRE.test(before)) || !lineEndRE.test(after);
const isMarkdownFile = linkedFile.extension === 'md';

const replaceTarget = (replacement: string) => {
text = before + replacement + after;
posOffset += replacement.length - prevLen;
};

if (!isMarkdownFile) {
// Skip link processing if we're not converting file links...
if (!settings.convertFileLinks) continue;

const adapter = app.vault.adapter as FileSystemAdapter;

// FYI: The mobile adapter also has getFullPath so this should work on mobile and desktop
// The mobile adapter isn't exported in the public API, however
if (!adapter.getFullPath) continue;
const fullPath = adapter.getFullPath(linkedFile.path);
const protocol = Platform.isWin ? 'file:///' : 'file://';
replaceTarget(`![](${protocol}${encodeURI(fullPath)})`);
continue;
}

// Replace the link with its text if the it's inline or would create an infinite loop
if (newAncestors.has(linkedFile) || isInline) {
replaceTarget(target.displayText || path);
continue;
}

// Recurse and bake the linked file...
const baked = sanitizeBakedContent(
await bake(app, linkedFile, subpath, newAncestors, settings)
);
replaceTarget(
listMatch ? applyIndent(stripFirstBullet(baked), listMatch[1]) : baked
);
}

return text;
}
4 changes: 4 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ const DEFAULT_SETTINGS: BakeSettings = {
convertFileLinks: true,
};

import { EasyBakeApi } from "./api";

export default class EasyBake extends Plugin {
settings: BakeSettings;

public api = new EasyBakeApi(this);

async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
Expand Down

0 comments on commit e0e86e8

Please sign in to comment.