From f26ea7c507e1ebcffb7d9a774f1852cd83061551 Mon Sep 17 00:00:00 2001 From: Amy Gale Ruth Bowersox Date: Wed, 14 Aug 2024 23:35:38 -0600 Subject: [PATCH] added options to have external links or non-Markdown files open in a new tab --- doc/configuration.toml | 4 ++++ src/dragonglass/config.py | 12 ++++++++++++ src/dragonglass/mparse.py | 26 +++++++++++++++++++++----- src/dragonglass/tree.py | 10 +++++----- 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/doc/configuration.toml b/doc/configuration.toml index 10d18f2..14ad2b0 100644 --- a/doc/configuration.toml +++ b/doc/configuration.toml @@ -5,6 +5,10 @@ prefix = "/" # If true, generate relative URLs for all internal URLs. Default is false. relative = false +# IF true, external links (full URLs) will be opened in a new tab. +extern-new-tab = false +# If true, "foreign" (non-Markdown) files will be opened in a new tab. +foreign-new-tab = false [classnames] # CSS class to use for an invalid reference. diff --git a/src/dragonglass/config.py b/src/dragonglass/config.py index 52547f2..f5fae20 100644 --- a/src/dragonglass/config.py +++ b/src/dragonglass/config.py @@ -98,6 +98,18 @@ class Context: metadata_section = self.config.get("metadata", {}) return metadata_section.get("description-title", False) + @property + def extern_in_new_tab(self) -> bool: + """Returns ``True`` if external links (full URLs) will e opened in a new tab, ``False`` if not.""" + links_section = self.config.get("links", {}) + return links_section.get("extern-new-tab", False) + + @property + def foreign_in_new_tab(self) -> bool: + """Returns ``True`` if non-Markdown local files will be opened in a new tab, ``False`` if not.""" + links_section = self.config.get("links", {}) + return links_section.get("foreign-new-tab", False) + @property def relative_links(self) -> bool: """ diff --git a/src/dragonglass/mparse.py b/src/dragonglass/mparse.py index d96fb4e..5dd25b5 100644 --- a/src/dragonglass/mparse.py +++ b/src/dragonglass/mparse.py @@ -401,7 +401,12 @@ class ObsidianLinks(Extension): """Returns the classname for invalid references.""" return self._context.get_classname('invalid-reference') - def _parse_reference(self, contents: str) -> tuple[str | None, str]: + @property + def extern_link_target(self) -> str: + """Returns the target attribute for external links, ot ``None`` if there is none.""" + return '_blank' if self._context.extern_in_new_tab else None + + def _parse_reference(self, contents: str) -> tuple[str | None, str, str | None]: """ Parse a reference and break it into link target and title. @@ -411,6 +416,7 @@ class ObsidianLinks(Extension): Returns: str: The link target, or ``None`` if the link is invalid. str: The link title to be used. + str: The target attribute for the resulting anchor, or ``None`` if not specified. """ contents = contents.replace(r'\|','|') # handle case where we're inside tables text = None @@ -435,8 +441,11 @@ class ObsidianLinks(Extension): self._context.current_node if self._context.relative_links else None) if hashloc: link = f"{link}#hdr-{hashloc}" - return link, text - return None, text + target_window = None + if not node.is_md and self._context.foreign_in_new_tab: + target_window = '_blank' + return link, text, target_window + return None, text, None class ObsidianLinksProc(InlineProcessor): """Processor that handles Obsidian links, [[page]].""" @@ -467,7 +476,7 @@ class ObsidianLinks(Extension): int: The index of the first character in ``data`` that was *not* consumed by the pattern, or ``None`` if the match was rejected. """ - link, text = self._extref._parse_reference(m.group(1)) + link, text, target = self._extref._parse_reference(m.group(1)) if link is None: el = etree.Element('span') el.set('class', self._extref.invalid_reference_classname) @@ -476,6 +485,8 @@ class ObsidianLinks(Extension): el = etree.Element('a') el.set('href', link) el.set('class', self._extref.obsidian_link_classname) + if target: + el.set('target', target) el.text = text return el, m.start(0), m.end(0) @@ -515,9 +526,12 @@ class ObsidianLinks(Extension): if is_proper_url(link): el = etree.Element('a') el.set('href', link) + target = self._extref.extern_link_target + if target: + el.set('target', target) el.text = text else: - newlink, _ = self._extref._parse_reference(sanitize_reference(link)) + newlink, _, target = self._extref._parse_reference(sanitize_reference(link)) if newlink is None: el = etree.Element('span') el.set('class', self._extref.invalid_reference_classname) @@ -526,6 +540,8 @@ class ObsidianLinks(Extension): el = etree.Element('a') el.set('href', newlink) el.set('class', self._extref.obsidian_link_classname) + if target: + el.set('target', target) el.text = text return el, m.start(0), m.end(0) diff --git a/src/dragonglass/tree.py b/src/dragonglass/tree.py index ec48081..9ebab1b 100644 --- a/src/dragonglass/tree.py +++ b/src/dragonglass/tree.py @@ -250,15 +250,15 @@ class SourceIndex: Returns: SourceNode: The node that was found, or ``None`` if the node was not found. - str: Indicates whether the match was on "NAME" or "ALIAS". Returns ``None`` if the node was not found. + str: Indicates whether the match was on "NAME", "ALIAS", or "PATH". Returns ``None`` + if the node was not found. """ if reference in self._byname: return self._byname[reference], 'NAME' elif reference in self._byalias: return self._byalias[reference], 'ALIAS' else: - new_path = from_node.path.parent / reference - s = new_path.as_posix() - if s in self._bypath: - return self._bypath[s], 'PATH' + new_path = (from_node.path.parent / reference).as_posix() + if new_path in self._bypath: + return self._bypath[new_path], 'PATH' return None, None