`processFrontMatter` creates undesired line break for long strings

It seems that for the strings longer than 79 characters, underlying YAML serializer inserts line break

So for line of length 80+ the following code

const string80 = 'a'.repeat(78) + ' b';

await app.fileManager.processFrontMatter(app.workspace.getActiveFile(), (frontMatter) => { frontMatter.key = string80; });

Makes

---
key: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  b
---

While for the string with 79- length, the line break is not created

const string79 = 'a'.repeat(77) + ' b';

await app.fileManager.processFrontMatter(app.workspace.getActiveFile(), (frontMatter) => { frontMatter.key = string79; });

makes

---
key: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b
---

While in both cases the generated YAML is valid, sometimes such line breaks are undesirable.

I have a suspicion that Obsidian internally uses js-yaml library with its dump method to serialize objects to YAML strings.

This dump method has lineWidth option which is by default is 80.

If you set it to -1, the undesired line break disappears.

I encourage Obsidian developers to think how to give us more control over this YAML serialization.

Thus we can configure

  • if we want line breaks or not
  • if we want to use single or double quotes
  • if we want arrays to be inline ['a', 'b' ,'c'] or multi-line
- a
- b
- c

etc

2 Likes

Yeah, the process Frontmatter do that but some of my workflow don’t support the yaml like that. Moreover, linter broke when the frontmatter is split.

I fav this post because allowing removing line break will be pretty usefull.

1 Like

Yes, I also have this issue with Linter.

I just wrote my own version of processFrontMatter to workaround this problem with line breaks

so instead of

await app.fileManager.processFrontMatter(file, fn);

I use

await customJS.FrontMatter.process(file, fn);

Here I use GitHub - saml-dev/obsidian-custom-js: An Obsidian plugin to allow users to reuse code blocks across all devices and OSes , but my code could be easily adopted to be used elsewhere.


class FrontMatter {
  async process(file, frontMatterFn) {
    const { load, dump } = await import('https://cdn.jsdelivr.net/npm/[email protected]/+esm');

    await app.vault.process(file, (content) => {
      const match = content.match(/^---\r?\n((?:.|\r?\n)*?)\r?\n?---(?:\r?\n|$)((?:.|\r?\n)*)/);
      let frontMatterStr;
      let mainContent;
      if (match) {
        frontMatterStr = match[1];
        mainContent = match[2];
      } else {
        frontMatterStr = '';
        mainContent = content;
      }

      if (!mainContent) {
        mainContent = '\n';
      } else {
        mainContent = '\n' + mainContent.trim() + '\n';
      }

      const frontMatter = load(frontMatterStr) ?? {};
      frontMatterFn(frontMatter);
      let newFrontMatterStr = dump(frontMatter, {
        lineWidth: -1,
        quotingType: '"'
      });
      if (newFrontMatterStr === '{}\n') {
        newFrontMatterStr = '';
      }

      const newContent = `---
${newFrontMatterStr}---
${mainContent}`;

      return newContent;
    });
  }
}
2 Likes

The reason this is problematic is that if you are not using the built in YAML processor for obsidian due to a lack of the ability to test the logic (like the Linter), then when you try to parse the YAML it is either not valid or is using a different spec.

Generally in YAML breaking things up across lines requires an indicator at the end of the line specifying that the content continues on the next line.

Good point, we will disable the max line width in the next insider build.

3 Likes

Why not accept a config object to let plugins set it up? Or at least user configuration

Sorry for focusing on the code, but how is supposed frontMatterFn going to provide new frontmatter values? By mutating the frontmatter object? Why not just take whatever it returns and use that instead? Your current approach makes hard to totally replace the frontmatter

Why not accept a config object to let plugins set it up? Or at least user configuration

Because if different plugins specify different configuration options, they will just override one another.

Your current approach makes hard to totally replace the frontmatter

This is by design. Plugins should avoid rewriting the entire frontmatter instance.

Do you mind detailing why?

I don’t mean a global configuration, Ia mean a configuration object per call. They will only override each other if they process the frontmatter, which should probably not be a general obsidian concern