Rymbrak / interactive-map

This extension for Visual Studio Code provides a webview for the use and creation of interactive maps with custom backgrounds based on Leaflet.
MIT License
1 stars 1 forks source link

[Feature request] Markdown support #1

Open KosRud opened 1 month ago

KosRud commented 1 month ago

Firstly, thanks for this awesome extension.

I have the following suggestion:

Part 1

Marker description can render markdown parsed by built-in markdown-it instance from vscode. To capture the built-in parser instance, your extension can masquerade as markdown extension, but instead of injecting a plugin it just saves the provided parser instance to a variable for later use.

Part 2

The second part might be a bit controversial and "ugly" from architectural standpoint, but it provides a great benefit (IMHO) which would be (comparably) difficult to achieve otherwise.

Instead of storing descriptions inside json file, they are stored externally as .md files. For example: each marker has a unique id, and an .md file with the name matching its id will contain corresponding description.

Why? Because vscode has a built-in setting for updating links in .md files when target file is moved or renamed: markdown.updateLinksOnFileMove.enabled. If you don't feel like re-implementing it, existing code can be leveraged by putting descriptions into .md files so vscode will take care of it.

This is how VSCode finds links in markdown files: link.

Single markdown file

Alternatively, all descriptions can be stored within a single markdown file:

# 1569753 Averdale (city)
population: 420
[details](/some/file.md)

## Districts

### Port
...

### Market
...

### Slums
...

# 1578552 Welwood forest

![image](/images/forest.png)

[details](/some/folder/welwood.md)

In these examples 1569753 and 1578552 are marker ids (followed by names). Note that I used h1 headings to define anchors: this is not ideal (such text could be contained within marker description as valid markdown) but from the top of my head I don't see a good solution that is guaranteed to never clash with valid markdown.

KosRud commented 1 month ago

Brainstorming format for markdown file:

<leaflet-marker markerId="1569753">

population: 420  
[details](/some/file.md)

# Districts

## Port
...

## Market
...

## Slums
...

</leaflet-marker>
<leaflet-marker markerId="1578552">

![image](/images/forest.png)  
[details](/some/folder/welwood.md)

</leaflet-marker>

Markdown supports using HTML tags. All built-in HTML elements use one word definition, so name leaflet-marker should be safe and future-proof. If the element leaflet-marker is not defined, the preview will just ignore it. But it can still be parsed easily with regex.

Attribute name is markerId to avoid clashing with id attribute in HTML.

KosRud commented 1 month ago

JSON inside markdown

To keep everything in one file, JSON map definition can be stored inside frontmatter at the beginning of markdown file. VSCode seems to include frontmatter plugin by default:

In the preview I've confirmed that frontmatter is not displayed.

Backup plan

As a backup plan if frontmatter is not viable for some reason, one could put JSON inside a code block at the very beginning of the markdown file.

Rymbrak commented 1 month ago

I'll look into how to implement these suggestions. The parsing shouldn't be too difficult, I had been working on unreleased integration with Dendron that can be adapted for that. Regarding Part 2: Thank you for the information, that is very useful for future consideration. It's something I wanted to implement, just didn't get around to yet. I think, I'll prefer having descriptions as separate files and use either links or ids to reference them.

KosRud commented 1 month ago

Separate files was my first thought, but I see one potential problem with it: polluting the file picker (Ctrl + P) and slowdown from things like updating links that have to go through all files in the workspace. To what extent it actually is a problem (if at all), I don't know.

KosRud commented 3 weeks ago

I've stumbled upon a problem when trying to capture MarkdownIt instance by masquerading as a markdown extension: the initialization of markdown extensions happens when you first open a markdown file, not when the extension is activated.

As a workaround, I made the extension check if MarkdownIt instance is already captured. If it is not, produce an error with a button that forces MarkdownIt instance capture:

if (md == null) {
  const option = await vscode.window.showErrorMessage(
    `Error: MarkdownIt instance was not captured`,
    'Force capture'
  );
  switch (option) {
    case 'Force capture':
      const mdCapturePromise = new Promise<void>((resolve) => {
        mdCaptureCallback = resolve;
        setTimeout(resolve, 3000);
      });

      await vscode.workspace
        .openTextDocument({
          content: `This is a dummy markdown document opened to force MardownIt initialization. This allows ${extensionNamePretty} to capture the MarkdownIt instance.`,
          language: 'markdown',
        })
        .then(async (doc) => {
          await vscode.window.showTextDocument(doc, {
            preview: false,
            viewColumn: vscode.ViewColumn.One,
          });
          await vscode.commands.executeCommand(
            'workbench.action.revertAndCloseActiveEditor'
          );
        });

      await mdCapturePromise;

      if (md == null) {
        await vscode.window.showErrorMessage(
          'MarkdownIt instance capture failed'
        );
        return;
      }

      await vscode.window.showInformationMessage(
        'MarkdownIt instance captured'
      );

      return;
    default:
      return;
  }
}
export function activate(context: vscode.ExtensionContext) {
    // init commands...

    return {
        extendMarkdownIt(markdownIt: any) {
            md = markdownIt;
            if (mdCaptureCallback) {
                mdCaptureCallback();
            }
            return markdownIt;
        },
    };
}

However, a better solution might be to use command markdown.api.render like mentioned here:

Rymbrak commented 3 weeks ago

I'll look into it after the next update for marker icons is out, probably within the next two weeks.

KosRud commented 3 weeks ago

Tried the command, this seems to work just fine:

const result = await vscode.commands.executeCommand<string>(
  'markdown.api.render',
  '# Test render'
);

Note: if tokens are needed (md.parse instead of md.render), command will not help.