webifier-extensions 1.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- webifier_extensions/_resources.py +11 -0
- webifier_extensions/analytics/google/extension.py +41 -0
- webifier_extensions/chapters/extension.py +11 -0
- webifier_extensions/chapters/renderer.py +50 -0
- webifier_extensions/comments/extension.py +11 -0
- webifier_extensions/comments/renderer.py +35 -0
- webifier_extensions/markdown/extension.py +18 -0
- webifier_extensions/markdown/renderer.py +27 -0
- webifier_extensions/notebook/converter.py +63 -0
- webifier_extensions/notebook/extension.py +54 -0
- webifier_extensions/people/extension.py +11 -0
- webifier_extensions/people/renderer.py +78 -0
- webifier_extensions/registry.py +25 -0
- webifier_extensions/resume/assets/css/resume.css +476 -0
- webifier_extensions/resume/assets/js/resume.js +29 -0
- webifier_extensions/resume/experience.html +246 -0
- webifier_extensions/resume/extension.py +54 -0
- webifier_extensions/resume/publications.html +36 -0
- webifier_extensions/resume/renderer.py +21 -0
- webifier_extensions/search/extension.py +19 -0
- webifier_extensions/standard/assets/css/codehilite.css +74 -0
- webifier_extensions/standard/assets/css/main.css +115 -0
- webifier_extensions/standard/assets/images/colab-badge.svg +1 -0
- webifier_extensions/standard/content_page.py +28 -0
- webifier_extensions/standard/extension.py +26 -0
- webifier_extensions/standard/freeform.py +39 -0
- webifier_extensions/standard/links.py +24 -0
- webifier_extensions/standard/page.py +46 -0
- webifier_extensions/standard/section.py +69 -0
- webifier_extensions/standard/templates/content.html +47 -0
- webifier_extensions/standard/templates/macros/chapters.html +40 -0
- webifier_extensions/standard/templates/macros/comments.html +21 -0
- webifier_extensions/standard/templates/macros/footer.html +33 -0
- webifier_extensions/standard/templates/macros/head.html +49 -0
- webifier_extensions/standard/templates/macros/header.html +35 -0
- webifier_extensions/standard/templates/macros/link.html +58 -0
- webifier_extensions/standard/templates/macros/links.html +18 -0
- webifier_extensions/standard/templates/macros/meta.html +12 -0
- webifier_extensions/standard/templates/macros/nav.html +175 -0
- webifier_extensions/standard/templates/macros/page_navigation.html +29 -0
- webifier_extensions/standard/templates/macros/people.html +23 -0
- webifier_extensions/standard/templates/macros/person.html +84 -0
- webifier_extensions/standard/templates/page.html +53 -0
- webifier_extensions/standard/templates/section.html +76 -0
- webifier_extensions/theme/assets/css/theme.css +220 -0
- webifier_extensions/theme/assets/js/theme.js +110 -0
- webifier_extensions/theme/extension.py +52 -0
- webifier_extensions-1.0.1.dist-info/METADATA +47 -0
- webifier_extensions-1.0.1.dist-info/RECORD +52 -0
- webifier_extensions-1.0.1.dist-info/WHEEL +4 -0
- webifier_extensions-1.0.1.dist-info/entry_points.txt +11 -0
- webifier_extensions-1.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def package_path(package: str, *parts: str) -> str:
|
|
8
|
+
spec = importlib.util.find_spec(package)
|
|
9
|
+
if not spec or not spec.submodule_search_locations:
|
|
10
|
+
raise ValueError(f"Cannot locate package resources for {package!r}.")
|
|
11
|
+
return os.path.join(next(iter(spec.submodule_search_locations)), *parts)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from markupsafe import Markup
|
|
4
|
+
from webifier.core.extensions import Extension, ExtensionContext
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GoogleAnalyticsExtension(Extension):
|
|
8
|
+
id = "webifier.analytics.google"
|
|
9
|
+
config_key = "google_analytics"
|
|
10
|
+
|
|
11
|
+
def register(self, ctx: ExtensionContext) -> None:
|
|
12
|
+
super().register(ctx)
|
|
13
|
+
ctx.add_hook("head", self.render_head)
|
|
14
|
+
|
|
15
|
+
def render_head(self, builder, *, config=None, **_kwargs) -> str:
|
|
16
|
+
analytics = {}
|
|
17
|
+
if isinstance(config, dict):
|
|
18
|
+
analytics = config.get("google_analytics", {})
|
|
19
|
+
if analytics is False or (
|
|
20
|
+
isinstance(analytics, dict) and analytics.get("enabled") is False
|
|
21
|
+
):
|
|
22
|
+
return ""
|
|
23
|
+
if not isinstance(analytics, dict):
|
|
24
|
+
analytics = {}
|
|
25
|
+
measurement_id = analytics.get("measurement_id") or analytics.get("id")
|
|
26
|
+
if not measurement_id:
|
|
27
|
+
return ""
|
|
28
|
+
return Markup(
|
|
29
|
+
"\n".join(
|
|
30
|
+
[
|
|
31
|
+
"<!-- Global site tag (gtag.js) - Google Analytics -->",
|
|
32
|
+
f'<script async src="https://www.googletagmanager.com/gtag/js?id={measurement_id}"></script>',
|
|
33
|
+
"<script>",
|
|
34
|
+
" window.dataLayer = window.dataLayer || [];",
|
|
35
|
+
" function gtag(){dataLayer.push(arguments);}",
|
|
36
|
+
" gtag('js', new Date());",
|
|
37
|
+
f" gtag('config', '{measurement_id}');",
|
|
38
|
+
"</script>",
|
|
39
|
+
]
|
|
40
|
+
)
|
|
41
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from webifier.core.extensions import Extension
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ChaptersExtension(Extension):
|
|
7
|
+
id = "webifier.chapters"
|
|
8
|
+
dependencies = ("webifier.standard",)
|
|
9
|
+
renderers = {
|
|
10
|
+
"chapters": "webifier_extensions.chapters.renderer.ChaptersRenderer",
|
|
11
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, ClassVar
|
|
4
|
+
|
|
5
|
+
from webifier.core.base import NodeContext, RendererModule
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ChaptersRenderer(RendererModule):
|
|
9
|
+
"""Render chapters as a Bootstrap accordion."""
|
|
10
|
+
|
|
11
|
+
template: ClassVar[str] = "macros/chapters.html"
|
|
12
|
+
META_KEYS: ClassVar[frozenset[str]] = frozenset(
|
|
13
|
+
{
|
|
14
|
+
"kind",
|
|
15
|
+
"template",
|
|
16
|
+
"label",
|
|
17
|
+
"background",
|
|
18
|
+
"style",
|
|
19
|
+
"content",
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def process(self, data: dict[str, Any], ctx: NodeContext, builder) -> dict[str, Any]:
|
|
24
|
+
"""Process each chapter's content recursively."""
|
|
25
|
+
processed = dict(data)
|
|
26
|
+
if "content" in processed and isinstance(processed["content"], list):
|
|
27
|
+
chapters = []
|
|
28
|
+
for i, chapter in enumerate(processed["content"]):
|
|
29
|
+
if isinstance(chapter, dict):
|
|
30
|
+
chapter_processed = {}
|
|
31
|
+
for key, value in chapter.items():
|
|
32
|
+
if key in ("title",):
|
|
33
|
+
chapter_processed[key] = value
|
|
34
|
+
else:
|
|
35
|
+
chapter_processed[key] = builder.process_node(
|
|
36
|
+
value, ctx.child(f"chapter-{i}")
|
|
37
|
+
)
|
|
38
|
+
chapters.append(chapter_processed)
|
|
39
|
+
else:
|
|
40
|
+
chapters.append(chapter)
|
|
41
|
+
processed["content"] = chapters
|
|
42
|
+
return processed
|
|
43
|
+
|
|
44
|
+
def render(self, data: dict[str, Any], ctx: NodeContext, builder) -> str:
|
|
45
|
+
template = builder.jinja_env.get_template(self.template)
|
|
46
|
+
return template.module.render_chapters(
|
|
47
|
+
data,
|
|
48
|
+
data.get("content", []),
|
|
49
|
+
ctx.depth,
|
|
50
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from webifier.core.extensions import Extension
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CommentsExtension(Extension):
|
|
7
|
+
id = "webifier.comments"
|
|
8
|
+
config_key = "comments"
|
|
9
|
+
renderers = {
|
|
10
|
+
"comments": "webifier_extensions.comments.renderer.CommentsRenderer",
|
|
11
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, ClassVar
|
|
4
|
+
|
|
5
|
+
from webifier.core.base import NodeContext, RendererModule
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CommentsRenderer(RendererModule):
|
|
9
|
+
"""Render an Utterances comment widget."""
|
|
10
|
+
|
|
11
|
+
template: ClassVar[str] = "macros/comments.html"
|
|
12
|
+
META_KEYS: ClassVar[frozenset[str]] = frozenset(
|
|
13
|
+
{
|
|
14
|
+
"kind",
|
|
15
|
+
"template",
|
|
16
|
+
"label",
|
|
17
|
+
"background",
|
|
18
|
+
"style",
|
|
19
|
+
"repo",
|
|
20
|
+
"issue_term",
|
|
21
|
+
"issue_label",
|
|
22
|
+
"theme",
|
|
23
|
+
"crossorigin",
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def process(self, data: dict[str, Any], ctx: NodeContext, builder) -> dict[str, Any]:
|
|
28
|
+
"""No children to process — just pass config through."""
|
|
29
|
+
# Inject global config for repo fallback
|
|
30
|
+
data["_config"] = builder.config
|
|
31
|
+
return data
|
|
32
|
+
|
|
33
|
+
def render(self, data: dict[str, Any], ctx: NodeContext, builder) -> str:
|
|
34
|
+
template = builder.jinja_env.get_template(self.template)
|
|
35
|
+
return template.module.render_comments(data, data.get("_config"))
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from webifier.core.extensions import Extension, ExtensionContext
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MarkdownExtension(Extension):
|
|
7
|
+
id = "webifier.markdown"
|
|
8
|
+
renderers = {
|
|
9
|
+
"markdown": "webifier_extensions.markdown.renderer.MarkdownRenderer",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
def register(self, ctx: ExtensionContext) -> None:
|
|
13
|
+
super().register(ctx)
|
|
14
|
+
for key in (".md", ".markdown", "md", "markdown"):
|
|
15
|
+
ctx.register_content_renderer(key, self.build_markdown_page)
|
|
16
|
+
|
|
17
|
+
def build_markdown_page(self, builder, src: str, ctx):
|
|
18
|
+
return builder._build_markdown_page(src, ctx)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, ClassVar
|
|
4
|
+
|
|
5
|
+
from webifier.core.base import NodeContext, RendererModule
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MarkdownRenderer(RendererModule):
|
|
9
|
+
"""Render a markdown string to HTML."""
|
|
10
|
+
|
|
11
|
+
template: ClassVar[str] = "" # No template — direct render
|
|
12
|
+
META_KEYS: ClassVar[frozenset[str]] = frozenset({"kind", "template"})
|
|
13
|
+
|
|
14
|
+
def process(self, data: dict[str, Any], ctx: NodeContext, builder) -> dict[str, Any]:
|
|
15
|
+
return data
|
|
16
|
+
|
|
17
|
+
def render(self, data: dict[str, Any], ctx: NodeContext, builder) -> str:
|
|
18
|
+
"""Render markdown content to HTML string."""
|
|
19
|
+
raw = data.get("content", "")
|
|
20
|
+
if not raw:
|
|
21
|
+
return ""
|
|
22
|
+
return builder.render_markdown(
|
|
23
|
+
raw,
|
|
24
|
+
assets_src_dir=ctx.assets_src_dir,
|
|
25
|
+
assets_target_dir=ctx.assets_target_dir,
|
|
26
|
+
search_links=ctx.search_links,
|
|
27
|
+
)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from webifier.core.frontmatter import split_yaml_front_matter
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def convert_notebook(builder, src: str, assets_dir: str) -> tuple[str, dict]:
|
|
9
|
+
"""Convert a Jupyter notebook to HTML body content.
|
|
10
|
+
|
|
11
|
+
Uses nbconvert to export, then extracts the notebook container
|
|
12
|
+
and post-processes HTML for asset resolution.
|
|
13
|
+
"""
|
|
14
|
+
from bs4 import BeautifulSoup
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from nbconvert import HTMLExporter
|
|
18
|
+
except ImportError as exc:
|
|
19
|
+
raise ImportError(
|
|
20
|
+
"nbconvert is required for notebook conversion. Install it with: pip install nbconvert"
|
|
21
|
+
) from exc
|
|
22
|
+
|
|
23
|
+
notebook, metadata = read_notebook_with_metadata(src)
|
|
24
|
+
|
|
25
|
+
exporter = HTMLExporter()
|
|
26
|
+
body, _ = exporter.from_notebook_node(notebook)
|
|
27
|
+
|
|
28
|
+
# Extract just the notebook content
|
|
29
|
+
soup = BeautifulSoup(body, "html.parser")
|
|
30
|
+
container = soup.find(id="notebook-container") or soup.find("body") or soup
|
|
31
|
+
content = str(container)
|
|
32
|
+
|
|
33
|
+
# Post-process HTML for asset resolution
|
|
34
|
+
from webifier.core.html import process_html
|
|
35
|
+
|
|
36
|
+
content = process_html(
|
|
37
|
+
builder,
|
|
38
|
+
content,
|
|
39
|
+
assets_src_dir=os.path.dirname(src),
|
|
40
|
+
assets_target_dir=assets_dir,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return content, metadata
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def read_notebook_with_metadata(src: str) -> tuple[object, dict]:
|
|
47
|
+
import nbformat
|
|
48
|
+
|
|
49
|
+
with open(src) as f:
|
|
50
|
+
notebook = nbformat.read(f, as_version=4)
|
|
51
|
+
|
|
52
|
+
cells = notebook.get("cells", [])
|
|
53
|
+
if not cells or cells[0].get("cell_type") != "markdown":
|
|
54
|
+
return notebook, {}
|
|
55
|
+
|
|
56
|
+
metadata, first_cell_body = split_yaml_front_matter(cells[0].get("source", ""))
|
|
57
|
+
if not metadata:
|
|
58
|
+
return notebook, {}
|
|
59
|
+
if first_cell_body.strip():
|
|
60
|
+
cells[0]["source"] = first_cell_body.lstrip("\n")
|
|
61
|
+
else:
|
|
62
|
+
notebook["cells"] = cells[1:]
|
|
63
|
+
return notebook, metadata
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from webifier.core.base import resolve_renderer
|
|
6
|
+
from webifier.core.extensions import Extension, ExtensionContext
|
|
7
|
+
from webifier.interface.io import prepend_baseurl, strip_suffixes
|
|
8
|
+
|
|
9
|
+
from .converter import convert_notebook
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class NotebookExtension(Extension):
|
|
13
|
+
id = "webifier.notebook"
|
|
14
|
+
dependencies = ("webifier.standard",)
|
|
15
|
+
|
|
16
|
+
def register(self, ctx: ExtensionContext) -> None:
|
|
17
|
+
super().register(ctx)
|
|
18
|
+
for key in (".ipynb", "notebook"):
|
|
19
|
+
ctx.register_content_renderer(key, self.build_notebook_page)
|
|
20
|
+
|
|
21
|
+
def build_notebook_page(self, builder, src: str, ctx):
|
|
22
|
+
if not os.path.isfile(src):
|
|
23
|
+
print(f" Warning: notebook file not found: {src}")
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
body_html, metadata = convert_notebook(builder, src, builder.assets_dir)
|
|
28
|
+
except Exception as exc:
|
|
29
|
+
print(f" Warning: notebook conversion failed for {src}: {exc}")
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
renderer = resolve_renderer("content-page", jinja_env=builder.jinja_env)
|
|
33
|
+
page_data = {
|
|
34
|
+
"content": body_html,
|
|
35
|
+
"metadata": metadata,
|
|
36
|
+
"title": metadata.get("title", os.path.basename(src)),
|
|
37
|
+
"page_url": prepend_baseurl(
|
|
38
|
+
strip_suffixes(src, builder._content_suffixes()),
|
|
39
|
+
builder.base_url,
|
|
40
|
+
),
|
|
41
|
+
"source_path": src,
|
|
42
|
+
}
|
|
43
|
+
if builder.root_data:
|
|
44
|
+
page_data["nav"] = builder.root_data.get("nav")
|
|
45
|
+
page_data["footer"] = builder.root_data.get("footer")
|
|
46
|
+
page_data["config"] = builder.config
|
|
47
|
+
if builder.repo_full_name:
|
|
48
|
+
nb_dir = os.path.dirname(src)
|
|
49
|
+
nb_name = strip_suffixes(os.path.basename(src), [".ipynb"])
|
|
50
|
+
page_data["colab"] = (
|
|
51
|
+
f"https://colab.research.google.com/github/"
|
|
52
|
+
f"{builder.repo_full_name}/blob/master/{nb_dir}/{nb_name}.ipynb"
|
|
53
|
+
)
|
|
54
|
+
return renderer.render(page_data, ctx, builder)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from webifier.core.extensions import Extension
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PeopleExtension(Extension):
|
|
7
|
+
id = "webifier.people"
|
|
8
|
+
dependencies = ("webifier.standard",)
|
|
9
|
+
renderers = {
|
|
10
|
+
"people": "webifier_extensions.people.renderer.PeopleRenderer",
|
|
11
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, ClassVar
|
|
4
|
+
|
|
5
|
+
from webifier.core.base import NodeContext, RendererModule
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PeopleRenderer(RendererModule):
|
|
9
|
+
"""Render a grid of people cards."""
|
|
10
|
+
|
|
11
|
+
template: ClassVar[str] = "macros/people.html"
|
|
12
|
+
META_KEYS: ClassVar[frozenset[str]] = frozenset(
|
|
13
|
+
{
|
|
14
|
+
"kind",
|
|
15
|
+
"template",
|
|
16
|
+
"label",
|
|
17
|
+
"background",
|
|
18
|
+
"style",
|
|
19
|
+
"content",
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def process(self, data: dict[str, Any], ctx: NodeContext, builder) -> dict[str, Any]:
|
|
24
|
+
"""Process each person entry."""
|
|
25
|
+
processed = dict(data)
|
|
26
|
+
if "content" in processed and isinstance(processed["content"], list):
|
|
27
|
+
people = []
|
|
28
|
+
for person in processed["content"]:
|
|
29
|
+
if isinstance(person, dict):
|
|
30
|
+
person = _process_person(person, ctx, builder)
|
|
31
|
+
people.append(person)
|
|
32
|
+
processed["content"] = people
|
|
33
|
+
return processed
|
|
34
|
+
|
|
35
|
+
def render(self, data: dict[str, Any], ctx: NodeContext, builder) -> str:
|
|
36
|
+
template = builder.jinja_env.get_template(self.template)
|
|
37
|
+
return template.module.render_people(data)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _process_person(person: dict, ctx: NodeContext, builder) -> dict:
|
|
41
|
+
"""Process a single person dict — resolve images, render bio."""
|
|
42
|
+
# Resolve profile image
|
|
43
|
+
if "image" in person:
|
|
44
|
+
img = person["image"]
|
|
45
|
+
if isinstance(img, str) and not img.startswith(("http", "data:")):
|
|
46
|
+
new_path = builder.files.copy_file(
|
|
47
|
+
img,
|
|
48
|
+
img,
|
|
49
|
+
src_dir=ctx.assets_src_dir,
|
|
50
|
+
target_dir=ctx.assets_target_dir or builder.assets_dir,
|
|
51
|
+
)
|
|
52
|
+
if new_path:
|
|
53
|
+
person["image"] = new_path
|
|
54
|
+
elif "github" in person:
|
|
55
|
+
person["image"] = f"https://github.com/{person['github']}.png"
|
|
56
|
+
|
|
57
|
+
# GitHub-derived image fallback
|
|
58
|
+
if "image" not in person and "github" in person:
|
|
59
|
+
person["image"] = f"https://github.com/{person['github']}.png"
|
|
60
|
+
|
|
61
|
+
# Render bio as markdown
|
|
62
|
+
if "bio" in person and isinstance(person["bio"], str):
|
|
63
|
+
person["bio"] = builder.render_markdown(
|
|
64
|
+
person["bio"],
|
|
65
|
+
assets_src_dir=ctx.assets_src_dir,
|
|
66
|
+
assets_target_dir=ctx.assets_target_dir,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Process contact links
|
|
70
|
+
if "contact" in person and isinstance(person["contact"], list):
|
|
71
|
+
processed_links = []
|
|
72
|
+
for link in person["contact"]:
|
|
73
|
+
if isinstance(link, dict):
|
|
74
|
+
link = builder._process_link(link, ctx)
|
|
75
|
+
processed_links.append(link)
|
|
76
|
+
person["contact"] = processed_links
|
|
77
|
+
|
|
78
|
+
return person
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .analytics.google.extension import GoogleAnalyticsExtension
|
|
4
|
+
from .chapters.extension import ChaptersExtension
|
|
5
|
+
from .comments.extension import CommentsExtension
|
|
6
|
+
from .markdown.extension import MarkdownExtension
|
|
7
|
+
from .notebook.extension import NotebookExtension
|
|
8
|
+
from .people.extension import PeopleExtension
|
|
9
|
+
from .resume.extension import ResumeExtension
|
|
10
|
+
from .search.extension import SearchExtension
|
|
11
|
+
from .standard.extension import StandardExtension
|
|
12
|
+
from .theme.extension import ThemeExtension
|
|
13
|
+
|
|
14
|
+
EXTENSIONS = {
|
|
15
|
+
"webifier.standard": StandardExtension,
|
|
16
|
+
"webifier.markdown": MarkdownExtension,
|
|
17
|
+
"webifier.notebook": NotebookExtension,
|
|
18
|
+
"webifier.search": SearchExtension,
|
|
19
|
+
"webifier.theme": ThemeExtension,
|
|
20
|
+
"webifier.analytics.google": GoogleAnalyticsExtension,
|
|
21
|
+
"webifier.comments": CommentsExtension,
|
|
22
|
+
"webifier.people": PeopleExtension,
|
|
23
|
+
"webifier.chapters": ChaptersExtension,
|
|
24
|
+
"webifier.resume": ResumeExtension,
|
|
25
|
+
}
|