{# Get the link type based on URL analysis Returns: "anchor", "internal", or "external" #} {% macro get_type(href) %} {%- set href_str = href|default('')|string -%} {%- if not href_str -%} internal {%- elif href_str is string_startingwith '#' -%} anchor {%- elif href_str is string_startingwith '/' and not (href_str is string_startingwith '//') -%} {# Relative URL starting with / is always internal #} internal {%- elif href_str is string_containing '://' or href_str is string_startingwith '//' -%} {# Has protocol or is protocol-relative - check domain #} {%- set current_domain = request.domain|default('')|lower -%} {%- set href_lower = href_str|lower -%} {# Extract domain from href #} {%- if href_lower is string_startingwith '//' -%} {%- set href_domain = href_lower|replace('//', '', 1)|split('/')|first -%} {%- elif href_lower is string_containing '://' -%} {%- set href_domain = href_lower|split('://')|last|split('/')|first -%} {%- else -%} {%- set href_domain = '' -%} {%- endif -%} {# Compare domains — exact match or a true subdomain (dot boundary). A plain substring check would treat e.g. "worksby.design.attacker.com" as internal, dropping rel="noopener" on a hostile link. #} {%- if current_domain and (href_domain == current_domain or href_domain is string_endingwith ('.' ~ current_domain)) -%} internal {%- else -%} external {%- endif -%} {%- else -%} {# No protocol - relative URL, always internal #} internal {%- endif -%} {% endmacro %} {# Get link attributes (target and rel) @param href - The URL string @param force_external - Force external behavior (optional, default false) @returns HTML attributes string #} {% macro attrs(href, force_external=false) %} {%- if force_external -%} target="_blank" rel="noopener" {%- else -%} {%- set link_type = get_type(href)|trim -%} {%- if link_type == 'external' -%} target="_blank" rel="noopener" {%- endif -%} {# Anchor and internal links: no target attribute (same tab) #} {%- endif -%} {% endmacro %} {# Simplified attrs that also works with HubSpot URL field objects Can accept either a string or a HubSpot URL field (with .href and .type) @param url - Either a string URL or HubSpot URL field object @returns HTML attributes string #} {% macro link_attrs(url) %} {%- if url is mapping -%} {# It's a HubSpot URL field object #} {%- set href = url.href|default('') -%} {%- else -%} {# It's a string #} {%- set href = url|default('') -%} {%- endif -%} {{ attrs(href) }} {% endmacro %} {# Full CTA link element @param href - The URL string @param text - Link text (will be escaped) @param class - CSS classes (optional) @param id - Element ID (optional) @returns Full element #} {% macro cta(href, text, class="", id="") %} {{ text|escape }} {% endmacro %} {# CTA button that accepts HubSpot URL field @param url - HubSpot URL field object (with .href) @param text - Button text @param style - Button style: "solid", "outline", or "ghost" @param extra_class - Additional CSS classes (optional) #} {% macro button(url, text, style="solid", extra_class="") %} {%- if url and url.href and text -%} {%- set btn_class = "button " -%} {%- if style == "solid" -%} {%- set btn_class = btn_class ~ "btn-primary" -%} {%- elif style == "outline" -%} {%- set btn_class = btn_class ~ "btn-secondary" -%} {%- else -%} {%- set btn_class = btn_class ~ "btn-ghost" -%} {%- endif -%} {%- if extra_class -%} {%- set btn_class = btn_class ~ " " ~ extra_class -%} {%- endif -%} {{ text|escape }} {%- endif -%} {% endmacro %} {# Text link (typically for secondary CTAs) @param url - HubSpot URL field object @param text - Link text @param class - CSS class (optional) @param show_arrow - Show arrow icon (optional, default true) #} {% macro text_link(url, text, class="", show_arrow=true) %} {%- if url and url.href and text -%} {{ text|escape }}{% if show_arrow %} {% endif %} {%- endif -%} {% endmacro %}