Repository: scalableminds/webknossos-libs

Issue:
[wk-lib] polish & publish common documentation
We want to have a common documentation for all libs in the wk-libs repository, which is published. This should consist both of guides/tutorials/… as well as API-documentation.

Therefore we should add doc-generation top-level and include all sub-packages there. A proposal for the stack, which is similar to our vx stack:
* [mkdocs](https://www.mkdocs.org) for guides etc and to integrate the API docs
* [Material Design Theme](https://squidfunk.github.io/mkdocs-material/)
* API docs either via
  * [pdoc](https://pdoc.dev) for API docs, see this [example for mkdocs integration](https://github.com/mitmproxy/pdoc/tree/main/examples/mkdocs), or
  * [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings)
  * more options are available: https://github.com/mkdocs/mkdocs/wiki/MkDocs-Plugins#api-documentation-building
 * Useful mkdocs extensions:
   * https://facelessuser.github.io/pymdown-extensions
* Nice example setup: https://github.com/ezsystems/developer-documentation/blob/master/mkdocs.yml
* For publishing we can either use S3, or github pages



Discussion:
#392 publishes the current docs to S3
Most of the setup will be done with #420, but further work will be needed, e.g.
* Show examples prominently
* Specific guides and tutorials
* Version selection (see https://squidfunk.github.io/mkdocs-material/setup/setting-up-versioning & https://github.com/squidfunk/mkdocs-material-example-versioning/tree/gh-pages)
* Auto-forward `/` to `/index.html`

Resolving patch:
diff --git a/docs/md_extensions/mdlightbox.py b/docs/md_extensions/mdlightbox.py
new file mode 100644
--- /dev/null
+++ b/docs/md_extensions/mdlightbox.py
@@ -0,0 +1,96 @@
+"""
+Extension for markbox to make all images lightbox
+
+The MIT License (MIT)
+
+Copyright (c) 2015 Alicia Schep
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE
+"""
+
+import markdown
+from markdown.treeprocessors import Treeprocessor
+from markdown import Extension
+from markdown.util import etree
+import re
+from copy import copy
+
+
+GROUP_RE = r'(^(@\{(?P<lightbox>.+)\})(?P<description>.*))'
+HIDDEN_RE = r'(^(!)(?P<description>.*))'
+
+class LightboxImagesTreeprocessor(Treeprocessor):
+    """ Lightbox Images Treeprocessor """
+    def __init__(self, md, group = True):
+        Treeprocessor.__init__(self, md)
+        self.group_re = re.compile(GROUP_RE)
+        self.hidden_re = re.compile(HIDDEN_RE)
+        self.group = group
+    def run(self, root):
+        parent_map = {c:p for p in root.iter() for c in p}
+        i = 0
+        images = root.getiterator("img")
+        for image in images:
+            desc = image.attrib["alt"]
+            h = self.hidden_re.match(desc)
+            if h:
+                desc = h.group("description")
+                hidden = True
+            else:
+                hidden = False
+            m = self.group_re.match(desc)
+            if m:
+                lb = m.group("lightbox")
+                desc = m.group("description")
+            elif self.group:
+                lb = "all_images"
+            else:
+                lb = "image" + str(i)
+            image.set("alt", desc)
+            parent = parent_map[image]
+            ix = list(parent).index(image)
+            new_node = etree.Element('a')
+            new_node.set("href",image.attrib["src"])
+            new_node.set("data-lightbox", lb)
+            new_node.set("data-title",desc)
+            new_node.tail = copy(image.tail)
+            parent.insert(ix, new_node)
+            parent.remove(image)
+            image.tail = markdown.util.AtomicString("")
+            if not hidden:
+                new_node.append(image) 
+            i += 1
+
+
+
+class LightboxImagesExtension(Extension):
+    """
+    LightboxImagesExtension
+    Extension class for markdown
+    """
+    def __init__(self, **kwargs):
+        self.config = {'group' : [True,"group all images into same lightbox"] }
+        super(LightboxImagesExtension, self).__init__(**kwargs)
+    def extendMarkdown(self, md, md_globals):
+        lightbox_images = LightboxImagesTreeprocessor(md, self.getConfig('group'))
+        md.treeprocessors.add("lightbox", lightbox_images, "_end")
+        md.registerExtension(self)
+
+def makeExtension(**kwargs):
+    return LightboxImagesExtension(**kwargs)
diff --git a/docs/md_extensions/video_embed_extension.py b/docs/md_extensions/video_embed_extension.py
new file mode 100644
--- /dev/null
+++ b/docs/md_extensions/video_embed_extension.py
@@ -0,0 +1,39 @@
+from markdown import Extension
+from markdown.inlinepatterns import Pattern
+
+VIDEO_LINK_RE = r'\!\[(?P<alt>[^\]]*)\]\((https?://(www\.|)'\
+                r'(youtube\.com/watch\?\S*v=(?P<video_id>[A-Za-z0-9_=-]+)(&t=(?P<start>[A-Za-z0-9_&=-]+)s)?)\S*)' \
+                r'(?<!png)(?<!jpg)(?<!jpeg)(?<!gif)\)'\
+
+class VideoEmbedExtension(Extension):
+  """
+  Embed Vimeo and Youtube videos in python markdown by using ![alt text](vimeo or youtube url)
+  """
+  def extendMarkdown(self, md):
+    link_pattern = VideoLink(VIDEO_LINK_RE, md)
+    link_pattern.ext = self
+    md.inlinePatterns.add('video_embed', link_pattern, '<image_link')
+
+class VideoLink(Pattern):
+  def handleMatch(self, match):
+    alt = match.group("alt").strip()
+    video_id = match.group("video_id")
+    start = match.group("start")
+    if video_id:
+      html = self.make_iframe(video_id.strip(), alt, start)
+      return self.markdown.htmlStash.store(html)
+    return None
+
+  def make_iframe(self, video_id, alt, start):
+    url = f"https://www.youtube-nocookie.com/embed/{video_id}"
+    if start:
+      start_time = int(start.strip())
+      url += f"?start={start_time}"
+    return (
+      f"<iframe class='youtube' src='{url}' alt='{alt}' "
+      +"frameborder='0' allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture' "
+      + "style='width: 500px; width: -webkit-fill-available; width: fill-available; width: stretch; aspect-ratio: 1.91;' allowfullscreen></iframe>"
+    )
+
+def makeExtension(**kwargs):
+    return VideoEmbedExtension(**kwargs)


Test patch: