snezhig / obsidian-front-matter-title

Plugin for Obsidian.md
GNU General Public License v3.0
233 stars 18 forks source link

Async Processor Function #223

Open josephalevin opened 4 weeks ago

josephalevin commented 4 weeks ago

Thank you for this plugin! How can I have a processor function update the title asynchronously after reading the contents of the note?

Use case: I want to use the first line of content in the note as the title. The file names are timestamps. I capture fleeting notes in my workflow from several sources so setting the first line as a header isn't an option.

I recognise there will may be a performance lag with opening all the files, but I assume it's an initial hit while the cache is loaded. I'm ok with that.

Here's what I'm trying:

Main template: title Fallback template: {{_path}}

Function V2:

const vault = window.app.vault;

// passed object has 2 params
const title = obj.title;
const path = obj.path;

//check for prefix on the fallback to avoid endless loop
if (title !== null && !title.startsWith("<MAGIC_TITLE>")){
  return title;
}

// the fallback to our magic callback hack
const file = vault.getFileByPath(path);

// test that it's really a file
if (file !== null) {
  const text = vault.cachedRead(file);

  // callback when the file contents are read
  text.then((value) => {
    //console.log(value);

    // TODO: find the first line of content

    // TODO: trim the first line if too long

    const magicTitle = 'TESTING MAGIC TITLE';

    // I NEED HELP HERE. HOW DO I GET THE MAGIC TITLE VALUE SET ASYNC?

  });

  // return a placeholder value until content is parsed
  return 'Loading...';
}
snezhig commented 4 weeks ago

Hi. I think it is not possible for now. Each feature resolves title through async, but all chain from resolving to processor and final result is a sync chain.

It takes some time to think about the options.

josephalevin commented 4 weeks ago

I've managed to hack together a working proof of concept by using my own cache and dispatching a metadata:cache:changed event to trick the plugin into resolving again so I can return my cached value syncronously

// passed object has 2 params
const title = obj.title;
const path = obj.path;

//check for prefix on the fallback to avoid endless loop
if (title !== null && !title.startsWith("<MAGIC_TITLE>")){
  return title;
}

//check if the magic link callback has set something in the cache
const plugin = window.app.plugins.plugins['obsidian-front-matter-title-plugin'];

// setup a hacky cache if null
var cache = plugin.magic_link_cache;
if (cache == null || cache == undefined){
  //console.log('init magic title cache');
  cache = new Map();
  plugin.magic_link_cache = cache;
}

//check the cache for a set title
if (cache.has(path)){
  const cachedTitle = cache.get(path);
  delete cache.delete(path);
  return cachedTitle;
}

// the fallback to our magic callback hack
const vault = window.app.vault;
const file = vault.getFileByPath(path);

// test that it's really a file
if (file !== null) {
  const text = vault.cachedRead(file);

  // callback when the file contents are read
  text.then((value) => {
    //console.log("reading note contents")

    // find the first line of content
    if (value == null || value == undefined){
      return;
    }

    const lines = value.split(/\r?\n/);
    let inFrontMatter = false;
    let magicTitle = 'untitled';

    // read the note file and find the first row of content after any front matter
    for (line of lines){
      line = line.trim();
      if (inFrontMatter){
        if (line.startsWith('---')){
          inFrontMatter = false;
        }
        continue;

      } else if (line.startsWith('---')){
        inFrontMatter = true;
        continue;
      }
      else if (line.length == 0 ){
        // skip empty lines
        continue;
      }

      // we found the magic title line! Now remove any leading markdown
      // Remove headings
      line = line.replace(/^\W*#+\W*/, "");

      // remove lists
      line = line.replace(/^\W*\+\W*/, "");

      magicTitle = line;
      break;

    }

    // TODO: trim the first line if too long

    //HACK: get the front-matter-title event dispatcher
    const dispatcher = window.app.plugins.plugins['obsidian-front-matter-title-plugin'].dispatcher;
    cache.set(path, magicTitle);

    const data = {
      path: path,
      cache: window.app.metadataCache
    };

    const event = {
      get() {return data;},
      stop() {}
    };

    // trick the plugin into refreshing to look at our cached value the next time this processor runs
    dispatcher.dispatch('metadata:cache:changed', event);

  });

  // return a placeholder value until content is parsed
  return 'loading...';
}
snezhig commented 4 weeks ago

Yea, you hacked it. :D