diff --git a/README.md b/README.md index 5b35e8c..1d979e0 100644 --- a/README.md +++ b/README.md @@ -379,6 +379,8 @@ markdown: enabled: true theme: ashen wrap: false + links: + external_new_tab: true blog: dir: blog ``` @@ -391,6 +393,7 @@ blog: | `markdown.syntax_highlighting.enabled` | Whether code should be highlighted. | | `markdown.syntax_highlighting.theme` | [Pygments] style for highlighting. | | `markdown.syntax_highlighting.wrap` | Whether the resulting code block should be word wrapped. | +| `markdown.links.external_new_tab` | Whether external links should be opened in a new tab. | | `blog.dir` | Name of a directory relative to `content/` whose children are automatically considered posts. | ### Sitemap diff --git a/src/zona/config.py b/src/zona/config.py index de3c985..63baac0 100644 --- a/src/zona/config.py +++ b/src/zona/config.py @@ -37,6 +37,11 @@ class HighlightingConfig: wrap: bool = False +@dataclass +class LinksConfig: + external_new_tab: bool = True + + @dataclass class MarkdownConfig: image_labels: bool = True @@ -44,6 +49,7 @@ class MarkdownConfig: syntax_highlighting: HighlightingConfig = field( default_factory=HighlightingConfig ) + links: LinksConfig = field(default_factory=LinksConfig) @dataclass diff --git a/src/zona/markdown.py b/src/zona/markdown.py index ce73016..5a71d3b 100644 --- a/src/zona/markdown.py +++ b/src/zona/markdown.py @@ -1,11 +1,10 @@ import xml.etree.ElementTree as etree from collections.abc import Sequence +from logging import Logger from pathlib import Path from typing import Any, override from l2m4m import LaTeX2MathMLExtension - -# from l2m4m import LaTeX2MathMLExtension from markdown import Markdown from markdown.extensions.abbr import AbbrExtension from markdown.extensions.attr_list import AttrListExtension @@ -34,8 +33,6 @@ from zona.log import get_logger from zona.metadata import Metadata from zona.models import Item -logger = get_logger() - class ZonaImageTreeprocessor(Treeprocessor): """Implement Zona's image caption rendering.""" @@ -43,6 +40,7 @@ class ZonaImageTreeprocessor(Treeprocessor): def __init__(self, md: Markdown): super().__init__() self.md: Markdown = md + self.logger: Logger = get_logger() @override def run(self, root: etree.Element): @@ -87,6 +85,7 @@ class ZonaLinkTreeprocessor(Treeprocessor): ): super().__init__() self.resolve: bool = resolve + self.logger: Logger = get_logger() if self.resolve: assert source is not None assert layout is not None @@ -103,9 +102,15 @@ class ZonaLinkTreeprocessor(Treeprocessor): if not href: continue if self.resolve: + assert self.config cur = Path(href) _href = href - if href.startswith("/"): + same_file = False + resolved = Path() + # href starting with anchor reference the current file + if href.startswith("#"): + same_file = True + elif href.startswith("/"): # resolve relative to content root resolved = ( self.layout.content / cur.relative_to("/") @@ -114,19 +119,25 @@ class ZonaLinkTreeprocessor(Treeprocessor): # treat as relative link and try to resolve resolved = (self.source.parent / cur).resolve() # only substitute if link points to an actual file - if resolved.exists(): + # that isn't the self file + if not same_file and resolved.exists(): item = self.item_map.get(resolved) if item: href = util.normalize_url(item.url) element.set("href", href) - logger.debug( + self.logger.debug( f"Link in file {self.source}: {_href} resolved to {href}" ) else: - logger.debug( + self.logger.debug( f"Warning: resolved path {resolved} not found in item map" ) - element.set("target", "_blank") + # open link in new tab if not self-link + elif ( + self.config.markdown.links.external_new_tab + and not same_file + ): + element.set("target", "_blank") def get_formatter(config: ZonaConfig):