{#
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 %}