Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Examples of use in Vue / docsify #13

Closed
phoenixweiss opened this issue Jan 28, 2024 · 20 comments
Closed

Examples of use in Vue / docsify #13

phoenixweiss opened this issue Jan 28, 2024 · 20 comments

Comments

@phoenixweiss
Copy link

Unfortunately, the codapi-js is not designed for situations where deferred rendering occurs, and large delay values cannot always help. There is no possibility of delayed initialization with binding to events yet.

Perhaps I'm missing something, so good examples would help significantly. So it would be extremely helpful to see usage examples in Vue/docsify.

P.S. Currently, for my tasks, I abandoned the widget in favor of my own Vue component(built into docsify) which directly sends a request to the codapi backend.

@nalgeon
Copy link
Owner

nalgeon commented Jan 28, 2024

Unfortunately, I am not familiar with Vue and have no intention of learning it at this time. Could you please provide a Vue-free example of the behavior you'd like the widget to support?

@phoenixweiss
Copy link
Author

I understand you perfectly and in any case, thank you very much for such a cool thing.

The problem with using codapi-js is not so much on the Vue plane, but rather in the way the widget is initialized. The delay approach is not always a good practice; it would be much more convenient to initialize on some event.

I’ll give an agnostic example that is not tied to Vue to understand the idea.

    // Add an event listener for the AfterMarkdownRendered event, which fires when the initial HTML document has been completely loaded and parsed
    document.addEventListener('AfterMarkdownRendered', function() {
      // Call the init() method of the codapi-js library after the DOM has been fully loaded and rendered as MD
      codapi.init();
    });

Obviously, the AfterMarkdownRendered event may be different in different libraries, and most likely it may not be associated with DOMContentLoaded directly as for plain HTML.

IMPORTANT that now the init() event is taken as an example, and there may be different cases, but if the widget had a public API, there would probably be different initialization levels (initAll() / init())

The technique for Vue 3 could be something like this:

    // Create a new Vue app
    const app = Vue.createApp({
      data() {
        return {
          markdownContent: ''
        }
      },
      mounted() {
        // Call the init() method of the codapi-js library after the Vue app has been mounted
        codapi.init();
      }
    });

    // Mount the Vue app to the #app element
    app.mount('#app');

We create a new Vue app and define a data property called markdownContent to store the Markdown content. Then iInside the mounted lifecycle hook, we call the init() method of the codapi-js library to initialize it. This ensures that the library is initialized after the Vue app has been mounted. Finally, we mount the Vue app to the #app element using app.mount('#app'). The Markdown content is rendered inside a <div> element using v-html directive, which allows us to dynamically bind and render HTML content from the markdownContent data property.

For docsify, we can implement this via custom js.

window.$docsify = {
  // ...
  mounted() {
    codapi.init();
  },
  // ...
};

In the index.html file, include the custom.js file after including the Docsify script:

<!-- ... -->
<script src="https://unpkg.com/docsify@latest"></script>
<script src="custom.js"></script>
<!-- ... -->

Now, when the page finishes rendering the Markdown content to HTML, the codapi.init() function will be called automatically.

Of course, these are crutches for now, but perhaps in the future a more graceful way will appear.

Requests/suggestions/corrections are welcome.

@nalgeon
Copy link
Owner

nalgeon commented Jan 30, 2024

Thanks for the detailed explanation! So basically what is required from codapi-js in this case is to export a proper init function. And this init should initialize all widget instances present on the page. Right?

@phoenixweiss
Copy link
Author

Thanks for the detailed explanation! So basically what is required from codapi-js in this case is to export a proper init function. And this init should initialize all widget instances present on the page. Right?

I think that's exactly it. Actually, it is important not to forget to provide an example in the documentation. As a general case, you can mention any markdown deferred rendering systems, you don’t have to refer to Vue / docsify.

BTW I'll try to help with testing and give feedback.

@nalgeon
Copy link
Owner

nalgeon commented Jan 30, 2024

Thanks a lot! I'll get back to you when I have something to show. Hopefully next week.

@nalgeon
Copy link
Owner

nalgeon commented Feb 6, 2024

Before refactoring the code, I tried to reproduce the problem with docsify, but everything seems to work fine, even without using init-delay. Here is what I did:

  1. Installed docsify:
mkdir hello-docsify
cd hello-docsify
npm i docsify-cli
npm exec docsify init ./docs
  1. Added Codapi to index.html:
<!DOCTYPE html>
<html lang="en">

<head>
    <!-- ... -->
    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css">
    <link rel="stylesheet" href="https://unpkg.com/@antonz/[email protected]/dist/snippet.css" />
</head>

<body>
    <div id="app"></div>
    <script>
        window.$docsify = {
            name: '',
            repo: ''
        }
    </script>
    <!-- Docsify v4 -->
    <script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
    <script defer src="https://unpkg.com/@antonz/[email protected]/dist/snippet.js"></script>
</body>

</html>
  1. Added a code snippet to README.md:
# Headline

> An awesome project.

```python
def greet(name):
  print(f"Hello, {name}!")

greet("World")
```

<codapi-snippet sandbox="python" editor="basic">
</codapi-snippet>
  1. Served the static content:
python3 -m http.server 3000
  1. Opened the docs in the browser:
http://localhost:3000/docs/#/

And the code snippet worked fine.

What am I missing?

@phoenixweiss
Copy link
Author

phoenixweiss commented Feb 7, 2024

What am I missing?

Thanks for the example, it's very valuable!

In my case, I use quite a lot of different extensions, it is likely that this is somehow connected.

here is just a small part of the index.html file:

...
  <script type="module">
    import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
    mermaid.initialize({ startOnLoad: true });
    window.mermaid = mermaid;
  </script>

  <script type="text/javascript">
    function resizeIframe(iframe) {
      iframe.height = iframe.contentWindow.document.body.scrollHeight + "px";
    }
  </script>

  <script src="//unpkg.com/[email protected]/dist/docsify-mermaid.js"></script>
  <script src="//unpkg.com/docsify-plantuml/dist/docsify-plantuml.min.js"></script>
  <script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
  <script src="//cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/blueimp-md5/2.10.0/js/md5.min.js"></script>
  <script src="//unpkg.com/docsify-copy-code@2"></script>
  <script src="//unpkg.com/docsify-scroll-to-top/dist/docsify-scroll-to-top.min.js"></script>
  <script src="//cdn.jsdelivr.net/npm/docsify-progress@latest/dist/progress.min.js"></script>
  <script src="//cdn.jsdelivr.net/npm/@sujaykumarh/[email protected]/dist/plugin.min.js"></script>
  <script src="//cdn.jsdelivr.net/npm/docsify-sidebar-collapse/dist/docsify-sidebar-collapse.min.js"></script>
  <script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/zoom-image.min.js"></script>
  <script src="//cdn.jsdelivr.net/npm/docsify-tabs@1"></script>
  <script src="//unpkg.com/docsify-plugin-flexible-alerts"></script>
  <script src="//unpkg.com/docsify-pagination/dist/docsify-pagination.min.js"></script>
  <script src="//unpkg.com/docsify-footer-enh/dist/docsify-footer-enh.min.js"></script>
  <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-bash.min.js"></script>
  <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-sql.min.js"></script>
  <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-json.min.js"></script>
  <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-jsx.min.js"></script>
  <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-python.min.js"></script>
  <script src="//cdn.jsdelivr.net/npm/katex@latest/dist/katex.min.js"></script>
  <script src="//cdn.jsdelivr.net/npm/marked@4"></script>
  <script src="//cdn.jsdelivr.net/npm/docsify-katex@latest/dist/docsify-katex.js"></script>
...

Obviously, in any case, waiting for everything to be loaded through delay is a worse solution than triggering the corresponding event. I mean that codapi-js may not keep up with any of the extensions that also affect the rendering of the code block.

In addition, the user can also use built-in Vue components and anything else. To keep the example agnostic, I think there is no point in going through a set of extensions for every possible case. If it is possible to somehow explicitly call initialization, the decision about exactly when to do this will remain at the discretion of the user.

To be fair, the problem for my specific case was already solved a couple of weeks ago, albeit in a slightly different way.

In addition, thanks to the open structure of the code, no one has canceled the possibility of using a monkey patch, since the structure of the CodapiSnippet class is quite explicit.

@phoenixweiss
Copy link
Author

phoenixweiss commented Feb 7, 2024

Or, for example, there may be a case where on a separate page there is a Vue component through which user authorization is implemented, and user will see some parts of the code examples in Mrakdown only after logging in.
In this case, it is also necessary to explicitly call initialization.

I understand that this may look unusual, but the ability to initialize a snippet not by a delay, but by a specific event, looks quite logical.

@nalgeon
Copy link
Owner

nalgeon commented Feb 7, 2024

The thing is, I don't understand how to reproduce the problem with Docsify regardless of the number of extensions. The codapi-snippet element comes after the code block, so by the time it's added to the DOM, the code block element should already be added (even without using init-delay).

My concern is that your initial problem may not be related to delayed initialization at all. Do you happen to have the exact error and stack trace? You never mentioned it.

@phoenixweiss
Copy link
Author

Ok, I will try to prepare an example on some platform as soon as I have time to do so.

@nalgeon
Copy link
Owner

nalgeon commented Feb 8, 2024

In the meantime, I've added the Docsify guide.

@nalgeon
Copy link
Owner

nalgeon commented Feb 13, 2024

Since Codapi now has a Docsify guide, I'll close this issue. But if you or anyone else encounters problems with Codapi on Docsify, I'll be happy to revisit it.

@nalgeon nalgeon closed this as completed Feb 13, 2024
@phoenixweiss
Copy link
Author

phoenixweiss commented Feb 13, 2024

Yes, thanks a lot!

I was just testing various cases on stackblitz.

First of all, most problems can occur with older versions of plugins or with their order (especially mermaid or katex plugins) that use render order hooks. However, I went a little further and tried to emulate a long loading time using my own plugin:

(function () {
  // Delay time in ms
  const delay = 5000;

  function docsifyMdRenderDelay(hook, vm) {
    // Invoked on each page load before new markdown is transformed to HTML.
    hook.beforeEach(function (markdown, next) {
      console.log(`Delay ${delay}ms start`);

      // Setting the delay using setTimeout
      setTimeout(function () {
        console.log(`Delay finish`);
        // Calling the next callback function with updated markdown
        next(markdown);
      }, delay);
    });
  }

  window.$docsify = window.$docsify || {};
  window.$docsify.plugins = [docsifyMdRenderDelay].concat(
    window.$docsify.plugins || []
  );
})();

If anyone is interested in playing around, just save this code, for example, in the file docsify-md-render-delay.js and then specify it in order to check the artificial delay in index.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <!-- ... -->
    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css">
    <link rel="stylesheet" href="https://unpkg.com/@antonz/[email protected]/dist/snippet.css" />
</head>

<body>
    <div id="app"></div>
    <script>
        window.$docsify = {
            name: '',
            repo: ''
        }
    </script>
    <!-- Docsify v4 -->
    <script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
    <!-- HERE --> <script src="docsify-md-render-delay.js"></script>
    <script defer src="https://unpkg.com/@antonz/[email protected]/dist/snippet.js"></script>
</body>

</html>

The delay activation stage can be associated with a specific life cycle stage via certain hook.

Tip

In general, the problem in the docsify case can be solved more simply: it is enough that the codapi snippet is loaded last in order. In most cases this solves any problems.

BTW, this does not mean that I will not wait for public methods to appear in the snippet API ;)

@nalgeon
Copy link
Owner

nalgeon commented Feb 14, 2024

Thanks a lot! I didn't expect you to follow up, people don't usually do that :)

I tried adding docsify-md-render-delay.js and it does indeed introduce a delay. But the widget still works fine no matter where I put the snippet.js script.

What am I missing?

@phoenixweiss
Copy link
Author

What am I missing?

I can’t say for sure yet, perhaps the environment influences it somehow.

deeper

I suggest abandoning this for now until someone else comes along with the same problem. Although this is interesting, I don’t think there will be an opportunity to go that deep in the near future.

Anyway, thanks a lot!

@nalgeon
Copy link
Owner

nalgeon commented Feb 14, 2024

Sure thing! Thank you very much Pavel, you've been very helpful. It's very rare to see such quality feedback.

@phoenixweiss
Copy link
Author

...docsify strikes back!!!

I don't want to create a new issue, but I probably found a problem with codapi-js in docsify using a template and can share a specific example:

https://stackblitz.com/edit/docsify-codapi-js-demo?file=docs%2Findex.md

Moreover, here the block with an example in python works well, but with an example in sqlite (directly from the documentation) it does not work, and this is a completely different problem than the one with which the discussion began. I think this is due to the template="#employees.sql" specification, based on the output in the dev-console:

2024-02-14_16-06-52

The first assumption was that this was due to the executeScript: true setting, but this turned out not to be the case. I continue to monitor, maybe I can find a workaround, but I can’t say for sure when.

@nalgeon
Copy link
Owner

nalgeon commented Feb 14, 2024

codapi-snippet should always be on two different lines. Not like this:

<codapi-snippet engine="wasi" sandbox="sqlite" editor="basic" template="#employees.sql"></codapi-snippet>

...but like this:

<codapi-snippet engine="wasi" sandbox="sqlite" editor="basic" template="#employees.sql">
</codapi-snippet>

Otherwise, it'll be rendered inside a <p> tag and won't be able to attach to the preceding code block.

@phoenixweiss
Copy link
Author

codapi-snippet should always be on two different lines.

Mystery solved, but it was not at all obvious...

Maybe add a notice about this to the documentation?

@nalgeon
Copy link
Owner

nalgeon commented Feb 14, 2024

Nobody reads the docs anyway, so I tried to fix this in code (release 0.14.0).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants