Absolue-Necessite/Projets/Scripts/replaceLinks.js

168 lines
5.7 KiB
JavaScript
Raw Normal View History

2023-04-03 20:35:26 +00:00
module.exports = {
// object that describes the step and its configuration
description: {
// the name of your step
name: "Replace links",
// short description of what it does
description: "Replaces wikilinks with the linked content",
// array. valid options are "Scene", "Manuscript", "Join". "Join" must be the only member if present.
availableKinds: ["Scene"],
// array of step options, or an empty array if step has no options
options: [
// a boolean option follows as another example
{
id: "removeH1",
name: "Remove h1 of linked content",
description: "If checked, the h1 headers of the linked content will be removed.",
type: "Boolean",
default: true,
},
{
id: "removeFrontmatter",
name: "Remove frontmatter of linked content",
description: "If checked, the frontmatter of the linked content will be removed.",
type: "Boolean",
default: true,
},
],
},
/**
Function that is executed during compilation. It may be `async`.
Errors encountered during execution should be thrown and will
be handled by Longform.
@param input If the step is of kind Scene or Join (see context),
this will be *an array* containing elements of type:
{
path: string; // path to scene
name: string; // file name of scene
contents: string; // text contents of scene
metadata: CachedMetadata; // Obsidian metadata of scene
indentationLevel?: number; // The indent level (starting at zero) of the scene
}
where each element corresponds to a scene (and thus the step has access to all scenes at once in `input`).
If the step is of kind Manuscript (see context), this will be of type:
{
// text contents of manuscript
contents: string;
}
@param context The execution context of the step, including the step
kind and option values:
{
kind: string; // "Scene" | "Join" | "Manuscript"
optionValues: { [id: string]: unknown } // Map of option IDs to values
projectPath: string; // path in vault to compiling project
draft: Draft; // The Draft type describing your project
app: App; // Obsidian app
}
@note For an example of using `context` to determine the shape of `input`, see
https://github.com/kevboh/longform/blob/main/src/compile/steps/strip-frontmatter.ts
@returns If of kind "Scene" or "Manuscript", the same shape as `input`
with the appropriate changes made to `contents`. If of kind "Join",
the same shape as a "Manuscript" step input.
*/
compile
};
async function compile(input, context) {
const files = await getAllFiles(context.app);
console.info("All files: ", files);
return Promise.all(
input.map(async scene => ({
...scene,
contents: await replaceLinksWithContent(scene.contents, scene.metadata, context, files, 0)
}))
)
}
function getAllFiles(app) {
return app.vault.getFiles();
}
async function replaceLinksWithContent(content, metadata, context, files, deep) {
if (deep > 1) {
return content;
}
console.info("Current file: ", metadata, "deep: ", deep);
const allLinks = [...(metadata.links || []), ...(metadata.embeds || [])];
return allLinks.reduce(async (accProm, item) => {
const acc = await accProm;
const [linkedFile, linkedContent] = await getLinkedContent(item.link, context.app, files);
if (!linkedContent) {
return acc;
}
const linkedMetadata = getMetadata(linkedFile, context.app);
2023-04-03 21:14:39 +00:00
let processedLinkedContent;
if (item.type === "embed") {
// Replace embed link with embed content
const embedProcessor = context.embedProviders.find(provider => provider.shouldProcessLink(item.link));
if (embedProcessor) {
const embedContent = await embedProcessor.process(item.link);
if (embedContent) {
processedLinkedContent = embedContent;
} else {
processedLinkedContent = item.original;
}
} else {
processedLinkedContent = item.original;
}
} else {
// Replace regular link with linked content
let contentArray = Array.from(linkedContent);
contentArray = removeFrontmatter(contentArray, linkedMetadata.frontmatter, context.optionValues);
contentArray = removeHeaders(contentArray, linkedMetadata.headings, context.optionValues)
const cleanedContent = contentArray.filter(val => val !== null)
.join("")
.trim();
processedLinkedContent = await replaceLinksWithContent(cleanedContent, linkedMetadata, context, files, deep + 1);
}
2023-04-03 20:35:26 +00:00
return acc.replaceAll(item.original, processedLinkedContent)
}, Promise.resolve(content));
}
async function getLinkedContent(linkText, app, files) {
const file = files.find(file => file.basename === linkText);
2023-04-03 21:14:39 +00:00
if (!file){
const file = await app.vault.getAbstractFileByPath(linkText);
}
2023-04-03 20:35:26 +00:00
return file
? [file, await app.vault.read(file)]
: [];
}
function getMetadata(file, app) {
return app.metadataCache.getFileCache(file)
}
function removeHeaders(content, headers, optionValues) {
if (optionValues.removeH1 && headers) {
return headers
.filter(header => header.level === 1)
.reduce((acc, header) => {
return removeFromTextArray(acc, header.position)
}, content)
} else {
return content;
}
}
function removeFrontmatter(content, frontmatter, optionValues) {
if (frontmatter && optionValues.removeFrontmatter) {
return removeFromTextArray(content, frontmatter.position);
} else {
return content;
}
}
function removeFromTextArray(text, position) {
return text.map((val, i) => i >= position.start.offset && i < position.end.offset ? null : val)
}