chrisgrieser / shimmering-obsidian

Alfred Workflow with dozens of features for controlling your Obsidian vault.
https://alfred.app/workflows/chrisgrieser/shimmering-obsidian/
MIT License
817 stars 40 forks source link

Feature Request: Prepend rather than append #175

Closed jamie9090 closed 1 month ago

jamie9090 commented 3 months ago

Checklist

Feature Requested

Thanks for this app, it's fantastic.

I would love to be able to "prepend" (start of note, but below frontmatter) rather than "append" (end of note).

Bonus points if we can even specify where to add the note after like QuickAdd.

Alternatives considered

I am currently using QuickAdd in Obsidian for this, but I believe Shimmering Obsidian could do this even better.

Relevant Screenshot

Hereis an example of Quickadd

CleanShot 2024-05-09 at 15 19 01@2x

macrospecter commented 1 month ago

For what it's worth, I was able to achieve something similar to this requested feature on a Mac by tweaking the workflow on my own. Here's what I did: I opened the workflow script append-to-note.js (by right-clicking on the name of the Workflow in Alfred preferences and selecting Open in Finder). I then edited the script in two ways, as follows. What I did here was switch around the order, in the "writeToFile" line in the "APPEND TO FILE" section, of the existing content of the note and the new content to be added. The first version just does that; the second version also adds spacing and a date to the prepended content, and that requires altering the "toAppend" constant. To work, the second version requires that you enter {date} in the Prefix field in the workflow's Configuration panel within Alfred preferences.

I'm a novice at coding and Alfred, so this might well be done more elegantly, or there may be concerning use cases, but this works for me!

`//─────────────────────────────────────────────────────────────────────────── // PREPEND TO FILE

if (!notePathHasHeading) {
    writeToFile(absolutePath, toAppend + "\n" + noteContent);
    return relativePath; // return for opening function
}

// determine heading location to append to
const lines = noteContent.split("\n");
const headingLineNo = lines
    .map((line) => {
        const lineIsHeading = line.match(/^#+ /);
        if (lineIsHeading) return line.replace(/^#+ /gm, "");
        return ""; // ensures that a line with the same content as a heading isn't detected
    })
    .indexOf(heading);

// guard: heading not found
if (headingLineNo === -1) {
    app.displayNotification("", { withTitle: "Heading not found.", subtitle: "Appending to the bottom of the note instead." })
    writeToFile(absolutePath, toAppend + "\n" + noteContent);
    return relativePath; // return for opening function
}

// Appending at last non-empty line of heading-section
let lastNonEmptyLineNo = -1;
for (let i = headingLineNo + 1; i < lines.length; i++) {
    const line = lines[i];
    if (isHeading(line)) break;
    else if (!isEmpty(line)) lastNonEmptyLineNo = i;
}
if (lastNonEmptyLineNo === -1) {
    ensureEmptyLineAt(lines, headingLineNo + 1);
    lines.splice(headingLineNo + 2, 0, toAppend);
    ensureEmptyLineAt(lines, headingLineNo + 3);
} else {
    lines.splice(lastNonEmptyLineNo + 1, 0, toAppend);
}
const content = lines.join("\n");
writeToFile(absolutePath, content);

return relativePath; // return for opening function

}`

`/* @type {AlfredRun} / // biome-ignore lint/correctness/noUnusedVariables: Alfred run function run(argv) { const vaultPath = $.getenv("vault_path");

// PREPARE AND READ FILE
let heading = "";
let relativePath = $.getenv("relative_path");
const toAppend1 = $.getenv("prefix");
const toAppend2 = argv[0];

const notePathHasHeading = /#[^ ][^/]*$/.test(relativePath);
if (notePathHasHeading) {
    const tempArr = relativePath.split("#");
    heading = tempArr.pop();
    relativePath = tempArr.join("");
}

let absolutePath = vaultPath + "/" + relativePath;
if (absolutePath.slice(-3) !== ".md") absolutePath += ".md";
if (!fileExists(absolutePath)) return "invalid"; // trigger error notification in Alfred

const noteContent = readFile(absolutePath);

//───────────────────────────────────────────────────────────────────────────
// PREPEND TO FILE

if (!notePathHasHeading) {
    writeToFile(absolutePath, toAppend1 + "\n" + toAppend2 + "\n" + "\n" + noteContent);
    return relativePath; // return for opening function
}

// determine heading location to append to
const lines = noteContent.split("\n");
const headingLineNo = lines
    .map((line) => {
        const lineIsHeading = line.match(/^#+ /);
        if (lineIsHeading) return line.replace(/^#+ /gm, "");
        return ""; // ensures that a line with the same content as a heading isn't detected
    })
    .indexOf(heading);

// guard: heading not found
if (headingLineNo === -1) {
    app.displayNotification("", { withTitle: "Heading not found.", subtitle: "Appending to the bottom of the note instead." })
    writeToFile(absolutePath, toAppend1 + "\n" + toAppend2 + "\n" + "\n" + noteContent);
    return relativePath; // return for opening function
}

// Appending at last non-empty line of heading-section
let lastNonEmptyLineNo = -1;
for (let i = headingLineNo + 1; i < lines.length; i++) {
    const line = lines[i];
    if (isHeading(line)) break;
    else if (!isEmpty(line)) lastNonEmptyLineNo = i;
}
if (lastNonEmptyLineNo === -1) {
    ensureEmptyLineAt(lines, headingLineNo + 1);
    lines.splice(headingLineNo + 2, 0, toAppend);
    ensureEmptyLineAt(lines, headingLineNo + 3);
} else {
    lines.splice(lastNonEmptyLineNo + 1, 0, toAppend);
}
const content = lines.join("\n");
writeToFile(absolutePath, content);

return relativePath; // return for opening function

} `

chrisgrieser commented 1 month ago

I am currently using QuickAdd in Obsidian for this, but I believe Shimmering Obsidian could do this even better.

Actually, it's the other way round: It is much easier to implement interactions with note content in an Obsidian plugin than in "Shimmering Obsidian", since the latter is an Alfred plugin and as such does not have direct access to the same Obsidian functionality a plugin has.

For example, when prepending text, plugins can easily check where the yaml frontmatter of a note ends via Obsidian, and add the text at that location. An Alfred workflow has no access to that kind of information, and thus would need to parse the note content itself to determine the correct location.

While not impossible to implement, such interactions are more easily (and more reliably) done via an Obsidian plugin. In this particular case, I'd recommend creating a feature request at QuickAdd to add a URI scheme, so you can use these instructions to trigger QuickAdd via Alfred. Not only is it much less work for the QuickAdd dev to add a URI scheme than for me to implement a prepend feature, you would get all the customizability of Quickadd at once.