Improved comments (see changelog)

This commit is contained in:
daudix
2024-10-23 04:35:15 +03:00
parent 62d43c9e71
commit 694479bcdb
17 changed files with 215 additions and 121 deletions

View File

@ -16,12 +16,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add missing translation strings for author separator and conjunctions. - Add missing translation strings for author separator and conjunctions.
- Add shortcode for Mastodon post embedding. - Add shortcode for Mastodon post embedding.
- Add support for Fuse.js search (#101). - Add support for Fuse.js search (#101).
- Add support for preview cards in comments.
- Add the ability to set video attributes via shortcode. - Add the ability to set video attributes via shortcode.
### Changed ### Changed
- **[BREAKING]** Rename `nav-bg` CSS variable to `glass-bg`. - **[BREAKING]** Rename `nav-bg` CSS variable to `glass-bg`.
- **[BREAKING]** Rename the visually hidden `hidden` class to `visually-hidden`. `hidden` is now used to completely hide the elements, including screen readers. - **[BREAKING]** Rename the visually hidden `hidden` class to `visually-hidden`. `hidden` is now used to completely hide the elements, including screen readers.
- Add default width/height to the `icon` class.
- Improve the look of threads in comments.
- Make `emoji` class available outside of comments. - Make `emoji` class available outside of comments.
- Make the code and styling for article cards much cleaner. - Make the code and styling for article cards much cleaner.
- Make the shortcodes code much cleaner. - Make the shortcodes code much cleaner.
@ -35,6 +38,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Fix article cards missing a focus outline. - Fix article cards missing a focus outline.
- Fix hover state of custom emojis in comments.
- Fix hover state of the "Load Comments" button.
- Fix navbar mods having broken border radius. - Fix navbar mods having broken border radius.
## [5.4.0](https://codeberg.org/daudix/duckquill/compare/v5.3.2...v5.4.0) ## [5.4.0](https://codeberg.org/daudix/duckquill/compare/v5.3.2...v5.4.0)

View File

@ -31,6 +31,10 @@ disclaimer = """
# user = "cassidy" # user = "cassidy"
# id = "112774854109302186" # id = "112774854109302186"
# #
# Thread with preview cards
# host = "mastodon.blaede.family"
# user = "cassidy"
# id = "110669429936617026"
# Post on GoToSocial # Post on GoToSocial
# #
# host = "alpha.polymaths.social" # host = "alpha.polymaths.social"

View File

@ -81,8 +81,6 @@ blockquote {
.icon { .icon {
vertical-align: -0.125em; vertical-align: -0.125em;
margin-inline-end: 0.25rem; margin-inline-end: 0.25rem;
width: 1rem;
height: 1rem;
} }
} }
} }

View File

@ -330,8 +330,6 @@
.icon { .icon {
vertical-align: -0.125em; vertical-align: -0.125em;
margin-inline-end: 0.25rem; margin-inline-end: 0.25rem;
width: 1rem;
height: 1rem;
} }
} }
@ -371,8 +369,6 @@
.icon { .icon {
transition: var(--transition); transition: var(--transition);
width: 1rem;
height: 1rem;
:root[dir*="rtl"] & { :root[dir*="rtl"] & {
transform: scaleX(-1); transform: scaleX(-1);

View File

@ -96,8 +96,6 @@
.icon { .icon {
transition: var(--transition); transition: var(--transition);
width: 1rem;
height: 1rem;
} }
} }

View File

@ -13,6 +13,19 @@ button.inline-button {
appearance: none; appearance: none;
cursor: pointer; cursor: pointer;
border: none; border: none;
&:disabled {
cursor: not-allowed;
&:hover {
background-color: var(--fg-muted-1);
color: var(--fg-muted-5);
}
&:active {
transform: none;
}
}
} }
a.inline-button, a.inline-button,

View File

@ -25,30 +25,21 @@
gap: 0.25rem; gap: 0.25rem;
margin-block-start: 2rem; margin-block-start: 2rem;
#load-comments { #load-comments:disabled {
--shimmer: rgb(from var(--accent-color) r g b / calc(var(--color-opacity) * 2)); --shimmer: rgb(from var(--accent-color) r g b / calc(var(--color-opacity) * 2));
animation: loading-shimmer var(--transition-long) ease-in-out alternate infinite;
transition: none;
background-image: linear-gradient(to right, var(--fg-muted-1) 50%, var(--shimmer) 75%, var(--fg-muted-1) 100%); background-image: linear-gradient(to right, var(--fg-muted-1) 50%, var(--shimmer) 75%, var(--fg-muted-1) 100%);
background-size: 200%; background-size: 200%;
background-color: transparent; background-color: transparent;
&:disabled { &:hover {
animation: loading-shimmer var(--transition-long) ease-in-out alternate infinite; background-color: transparent;
transition: none; }
cursor: not-allowed;
&:hover { @keyframes loading-shimmer {
background-color: transparent; to {
color: var(--fg-muted-5); background-position-x: -200%;
}
&:active {
transform: none;
}
@keyframes loading-shimmer {
to {
background-position-x: -200%;
}
} }
} }
} }
@ -84,6 +75,16 @@
border-radius: 0.25rem; border-radius: 0.25rem;
border-inline-start: 0.25rem solid var(--fg-muted-2); border-inline-start: 0.25rem solid var(--fg-muted-2);
padding-inline-start: 1rem; padding-inline-start: 1rem;
&:has(+ .comment-reply) {
border-end-start-radius: 0;
}
& + .comment-reply {
margin-block-start: -2rem;
border-start-start-radius: 0;
padding-block-start: 2rem;
}
} }
.avatar-link { .avatar-link {
@ -140,7 +141,7 @@
&.op { &.op {
background-color: var(--accent-color-alpha); background-color: var(--accent-color-alpha);
padding-inline-start: 0.625rem; padding-inline-start: 0.4375rem;
color: var(--accent-color); color: var(--accent-color);
&:hover { &:hover {
@ -153,17 +154,18 @@
} }
&::before { &::before {
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12'%3E%3Cpath d='m1 7 3 3 7-8' style='fill:none;stroke:%23000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none'/%3E%3C/svg%3E"); // --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12'%3E%3Cpath d='m1 7 3 3 7-8' style='fill:none;stroke:%23000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none'/%3E%3C/svg%3E");
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath d='M5.21 1.27A3.7 3.7 0 0 1 8 0c1.113 0 2.11.492 2.79 1.27a3.68 3.68 0 0 1 2.866 1.074A3.68 3.68 0 0 1 14.73 5.21C15.54 5.914 16 6.93 16 8s-.46 2.086-1.27 2.79a3.68 3.68 0 0 1-1.074 2.866 3.68 3.68 0 0 1-2.867 1.074C10.086 15.54 9.07 16 8 16s-2.086-.46-2.79-1.27a3.68 3.68 0 0 1-2.866-1.074A3.68 3.68 0 0 1 1.27 10.79 3.7 3.7 0 0 1 0 8c0-1.113.492-2.11 1.27-2.79a3.68 3.68 0 0 1 1.074-2.866A3.68 3.68 0 0 1 5.21 1.27m5.75 5.242a.613.613 0 0 0-.437-.98.61.61 0 0 0-.562.265L7.305 9.512 5.973 8.18a.616.616 0 0 0-.868.87l1.844 1.845a.61.61 0 0 0 .485.18.63.63 0 0 0 .453-.255zm0 0'/%3E%3C/svg%3E");
-webkit-mask-image: var(--icon); -webkit-mask-image: var(--icon);
display: inline-block; display: inline-block;
vertical-align: -0.0625rem; vertical-align: -0.1875rem;
mask-image: var(--icon); mask-image: var(--icon);
mask-size: cover; mask-size: cover;
transition: var(--transition); transition: var(--transition);
margin-inline-end: 0.25rem; margin-inline-end: 0.25rem;
background-color: var(--accent-color); background-color: var(--accent-color);
width: 0.75rem; width: 1rem;
height: 0.75rem; height: 1rem;
content: ""; content: "";
} }
@ -245,6 +247,57 @@
} }
} }
.card {
transition: var(--transition);
margin-block-start: 1rem;
box-shadow: var(--edge-highlight);
border-radius: var(--rounded-corner);
background-color: var(--fg-muted-1);
width: min(calc(var(--container-width) / 2), 100%);
overflow: hidden;
font-weight: normal;
text-decoration: none;
&:hover {
background-color: var(--fg-muted-2);
}
&:active {
transform: var(--active);
}
figure {
display: flex;
flex-direction: row;
margin: 0;
img {
margin: 0;
box-shadow: none;
border-radius: 0;
width: min(calc(var(--container-width) / 4), 50%);
}
figcaption {
display: flex;
flex-direction: column;
justify-content: center;
gap: 0.5rem;
padding: 1rem;
color: var(--fg-color);
font-size: var(--font-size-medium);
text-align: start;
p {
margin-block-start: 0.25rem;
margin-block-end: 0;
color: var(--fg-muted-5);
font-size: var(--font-size-small);
}
}
}
}
footer { footer {
display: flex; display: flex;
grid-area: interactions; grid-area: interactions;
@ -257,6 +310,7 @@
border-radius: 999px; border-radius: 999px;
background-color: transparent; background-color: transparent;
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
padding-inline-start: 0.625rem;
line-height: 1; line-height: 1;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
text-decoration: none; text-decoration: none;
@ -265,8 +319,6 @@
vertical-align: -0.125em; vertical-align: -0.125em;
transition: var(--transition-longer); transition: var(--transition-longer);
margin-inline-end: 0.25rem; margin-inline-end: 0.25rem;
width: 1rem;
height: 1rem;
} }
&:hover { &:hover {

View File

@ -1,9 +1,12 @@
.emoji { .emoji {
all: unset;
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: bottom;
transition: var(--transition); transition: var(--transition);
cursor: zoom-in; cursor: zoom-in;
margin: 0;
box-shadow: none;
border-radius: 0;
background-color: transparent;
width: 1.5em; width: 1.5em;
height: 1.5em; height: 1.5em;

View File

@ -2,6 +2,8 @@ i.icon {
display: inline-block; display: inline-block;
mask-size: cover; mask-size: cover;
background-color: currentColor; background-color: currentColor;
width: 1rem;
height: 1rem;
font-style: normal; font-style: normal;
font-variant: normal; font-variant: normal;
line-height: 0; line-height: 0;

View File

@ -104,7 +104,7 @@ video {
img { img {
transition: var(--transition-longer); transition: var(--transition-longer);
&:not(.no-hover, .full-bleed, [src*="#no-hover"], [src*="#full-bleed"]) { &:not(.no-hover, .full-bleed, [src*="#no-hover"], [src*="#full-bleed"], .emoji) {
cursor: zoom-in; cursor: zoom-in;
&:hover { &:hover {

View File

@ -123,8 +123,6 @@
mask-image: var(--icon); mask-image: var(--icon);
transition: var(--transition); transition: var(--transition);
margin-inline-end: 0.25rem; margin-inline-end: 0.25rem;
width: 1rem;
height: 1rem;
} }
} }
@ -169,8 +167,6 @@
.icon { .icon {
vertical-align: -0.125em; vertical-align: -0.125em;
transition: var(--transition); transition: var(--transition);
width: 1rem;
height: 1rem;
} }
} }

View File

@ -55,8 +55,6 @@
-webkit-mask-image: var(--icon); -webkit-mask-image: var(--icon);
mask-image: var(--icon); mask-image: var(--icon);
transition: var(--transition); transition: var(--transition);
width: 1rem;
height: 1rem;
:root[dir*="rtl"] & { :root[dir*="rtl"] & {
transform: scaleX(-1); transform: scaleX(-1);

View File

@ -25,8 +25,6 @@
.icon { .icon {
margin-inline-end: 0.375rem; margin-inline-end: 0.375rem;
width: 1rem;
height: 1rem;
} }
} }

View File

@ -105,6 +105,12 @@ function loadComments() {
status.content = emojify(status.content, status.emojis); status.content = emojify(status.content, status.emojis);
let comment = document.createElement("article");
comment.id = `comment-${status.id}`;
comment.className = isReply ? "comment comment-reply" : "comment";
comment.setAttribute("itemprop", "comment");
comment.setAttribute("itemtype", "http://schema.org/Comment");
let avatarSource = document.createElement("source"); let avatarSource = document.createElement("source");
avatarSource.setAttribute( avatarSource.setAttribute(
"srcset", "srcset",
@ -143,6 +149,7 @@ function loadComments() {
`${viewProfileText} @${status.account.username}@${instance}` `${viewProfileText} @${status.account.username}@${instance}`
); );
avatar.appendChild(avatarPicture); avatar.appendChild(avatarPicture);
comment.appendChild(avatar);
let instanceBadge = document.createElement("a"); let instanceBadge = document.createElement("a");
instanceBadge.className = "instance"; instanceBadge.className = "instance";
@ -164,6 +171,7 @@ function loadComments() {
header.className = "author"; header.className = "author";
header.appendChild(display); header.appendChild(display);
header.appendChild(instanceBadge); header.appendChild(instanceBadge);
comment.appendChild(header);
let permalink = document.createElement("a"); let permalink = document.createElement("a");
permalink.setAttribute("href", status.url); permalink.setAttribute("href", status.url);
@ -181,10 +189,12 @@ function loadComments() {
timestamp.setAttribute("datetime", status.created_at); timestamp.setAttribute("datetime", status.created_at);
timestamp.appendChild(permalink); timestamp.appendChild(permalink);
permalink.classList.add("external"); permalink.classList.add("external");
comment.appendChild(timestamp);
let main = document.createElement("main"); let main = document.createElement("main");
main.setAttribute("itemprop", "text"); main.setAttribute("itemprop", "text");
main.innerHTML = status.content; main.innerHTML = status.content;
comment.appendChild(main);
let attachments = status.media_attachments; let attachments = status.media_attachments;
let SUPPORTED_MEDIA = ["image", "video", "gifv", "audio"]; let SUPPORTED_MEDIA = ["image", "video", "gifv", "audio"];
@ -267,6 +277,8 @@ function loadComments() {
media.appendChild(mediaLink); media.appendChild(mediaLink);
} }
}); });
comment.appendChild(media);
} }
let interactions = document.createElement("footer"); let interactions = document.createElement("footer");
@ -292,25 +304,41 @@ function loadComments() {
faves.appendChild(favesIcon); faves.appendChild(favesIcon);
faves.insertAdjacentHTML('beforeend', ` ${status.favourites_count}`); faves.insertAdjacentHTML('beforeend', ` ${status.favourites_count}`);
interactions.appendChild(faves); interactions.appendChild(faves);
let comment = document.createElement("article");
comment.id = `comment-${status.id}`;
comment.className = isReply ? "comment comment-reply" : "comment";
comment.setAttribute("itemprop", "comment");
comment.setAttribute("itemtype", "http://schema.org/Comment");
comment.appendChild(avatar);
comment.appendChild(header);
comment.appendChild(timestamp);
comment.appendChild(main);
if (
attachments &&
Array.isArray(attachments) &&
attachments.length > 0
) {
comment.appendChild(media);
}
comment.appendChild(interactions); comment.appendChild(interactions);
if (status.card != null) {
let cardFigure = document.createElement("figure");
if (status.card.image != null) {
let cardImg = document.createElement("img");
cardImg.setAttribute("src", status.card.image);
cardImg.classList.add("no-hover");
cardFigure.appendChild(cardImg);
}
let cardCaption = document.createElement("figcaption");
let cardTitle = document.createElement("strong");
cardTitle.innerHTML = status.card.title;
cardCaption.appendChild(cardTitle);
if (status.card.description != null && status.card.description.length > 0) {
let cardDescription = document.createElement("p");
cardDescription.innerHTML = status.card.description;
cardCaption.appendChild(cardDescription);
}
cardFigure.appendChild(cardCaption);
let card = document.createElement("a");
card.className = "card";
card.setAttribute("href", status.card.url);
card.setAttribute("rel", relAttributes);
card.appendChild(cardFigure);
comment.appendChild(card);
}
if (op === true) { if (op === true) {
comment.classList.add("op"); comment.classList.add("op");

View File

@ -135,62 +135,7 @@
</article> </article>
{%- if page.extra.comments.id -%} {%- if page.extra.comments.id -%}
{%- if page.extra.comments.host -%} {%- include "partials/comments.html" -%}
{%- set host = page.extra.comments.host -%}
{%- else -%}
{%- set host = config.extra.comments.host -%}
{%- endif -%}
{%- if page.extra.comments.user -%}
{%- set user = page.extra.comments.user -%}
{%- else %}
{%- set user = config.extra.comments.user -%}
{%- endif -%}
{%- set id = page.extra.comments.id -%}
{%- set date_locale = macros_translate::translate(key="date_locale", default="en-US", language_strings=language_strings) | replace(from="_", to="-") -%}
<span id="rel-attributes" class="hidden">{{ rel_attributes }}</span>
<span id="host" class="hidden">{{ host }}</span>
<span id="user" class="hidden">{{ user }}</span>
<span id="id" class="hidden">{{ id }}</span>
<span id="date-locale" class="hidden">{{ date_locale }}</span>
<span id="loading-text" class="hidden">{{ macros_translate::translate(key='loading', default='Loading', language_strings=language_strings) }}…</span>
<span id="reload-text" class="hidden">{{ macros_translate::translate(key='reload', default='Reload', language_strings=language_strings) }}</span>
<span id="view-profile-text" class="hidden">{{ macros_translate::translate(key="view_profile", default="View Profile At", language_strings=language_strings) }}</span>
<span id="view-comment-text" class="hidden">{{ macros_translate::translate(key="view_comment", default="View Comment At", language_strings=language_strings) }}</span>
<span id="boosts-from-text" class="hidden">{{ macros_translate::translate(key="boosts_from", default="Boosts from $INSTANCE", language_strings=language_strings) }}</span>
<span id="faves-from-text" class="hidden">{{ macros_translate::translate(key="faves_from", default="Favorites from $INSTANCE", language_strings=language_strings) }}</span>
<span id="blog-post-author-text" class="hidden">{{ macros_translate::translate(key='blog_post_author', default='Blog post author', language_strings=language_strings) }}</span>
<span id="no-comments-text" class="hidden">{{ macros_translate::translate(key='no_comments', default='No Comments yet :/', language_strings=language_strings) }}</span>
<section id="comments">
{%- if config.extra.comments.show_qr -%}
<img
id="qrcode"
class="pixels no-hover"
title="{{ macros_translate::translate(key='comments_qr', default='QR code to a Mastodon post', language_strings=language_strings) }}"
{%- if config.markdown.lazy_async_image -%}decoding="async" loading="lazy"{%- endif -%}
src="https://api.qrserver.com/v1/create-qr-code/?data=https://{{ host }}/@{{ user }}/{{ id }}"
/>
{%- endif -%}
<h2>{{ macros_translate::translate(key="comments", default="Comments", language_strings=language_strings) }}</h2>
<p>{{ macros_translate::translate(key="comments_description", default="You can comment on this blog post by publicly replying to this post using a Mastodon or other ActivityPub/Fediverse account. Known non-private replies are displayed below.", language_strings=language_strings) }}</p>
<div class="dialog-buttons">
<button id="load-comments" class="inline-button">
{{- macros_translate::translate(key="load_comments", default="Load Comments", language_strings=language_strings) -}}
</button>
<a class="inline-button colored external" href="https://{{ host }}/@{{ user }}/{{ id }}" rel="{{ rel_attributes }}">
{{- macros_translate::translate(key="open_post", default="Open Post", language_strings=language_strings) -}}
</a>
</div>
<div id="comments-wrapper">
<noscript>
<p>{{ macros_translate::translate(key="comments_noscript", default="Loading comments relies on JavaScript. Try enabling JavaScript and reloading, or visit the original post on Mastodon.", language_strings=language_strings) }}</p>
</noscript>
</div>
</section>
{%- endif -%} {%- endif -%}
{%- if page.lower or page.higher -%} {%- if page.lower or page.higher -%}

View File

@ -0,0 +1,61 @@
{%- if page.extra.comments.host -%}
{%- set host = page.extra.comments.host -%}
{%- else -%}
{%- set host = config.extra.comments.host -%}
{%- endif -%}
{%- if page.extra.comments.user -%}
{%- set user = page.extra.comments.user -%}
{%- else %}
{%- set user = config.extra.comments.user -%}
{%- endif -%}
{%- set id = page.extra.comments.id -%}
{%- set date_locale = macros_translate::translate(key="date_locale", default="en-US", language_strings=language_strings) | replace(from="_", to="-") -%}
<span id="rel-attributes" class="hidden">{{ rel_attributes }}</span>
<span id="host" class="hidden">{{ host }}</span>
<span id="user" class="hidden">{{ user }}</span>
<span id="id" class="hidden">{{ id }}</span>
<span id="date-locale" class="hidden">{{ date_locale }}</span>
<span id="loading-text" class="hidden">{{ macros_translate::translate(key='loading', default='Loading', language_strings=language_strings) }}…</span>
<span id="reload-text" class="hidden">{{ macros_translate::translate(key='reload', default='Reload', language_strings=language_strings) }}</span>
<span id="view-profile-text" class="hidden">{{ macros_translate::translate(key="view_profile", default="View Profile At", language_strings=language_strings) }}</span>
<span id="view-comment-text" class="hidden">{{ macros_translate::translate(key="view_comment", default="View Comment At", language_strings=language_strings) }}</span>
<span id="boosts-from-text" class="hidden">{{ macros_translate::translate(key="boosts_from", default="Boosts from $INSTANCE", language_strings=language_strings) }}</span>
<span id="faves-from-text" class="hidden">{{ macros_translate::translate(key="faves_from", default="Favorites from $INSTANCE", language_strings=language_strings) }}</span>
<span id="blog-post-author-text" class="hidden">{{ macros_translate::translate(key='blog_post_author', default='Blog post author', language_strings=language_strings) }}</span>
<span id="no-comments-text" class="hidden">{{ macros_translate::translate(key='no_comments', default='No Comments yet :/', language_strings=language_strings) }}</span>
{%- if page.extra.comments.uri -%}
{%- set uri = page.extra.comments.uri -%}
<span id="post-uri" style="display: none;">{{ uri }}</span>
{%- endif -%}
<section id="comments">
{%- if config.extra.comments.show_qr -%}
<img
id="qrcode"
class="pixels no-hover"
title="{{ macros_translate::translate(key='comments_qr', default='QR code to a Mastodon post', language_strings=language_strings) }}"
{%- if config.markdown.lazy_async_image -%}decoding="async" loading="lazy"{%- endif -%}
src="https://api.qrserver.com/v1/create-qr-code/?data=https://{{ host }}/@{{ user }}/{{ id }}"
/>
{%- endif -%}
<h2>{{ macros_translate::translate(key="comments", default="Comments", language_strings=language_strings) }}</h2>
<p>{{ macros_translate::translate(key="comments_description", default="You can comment on this blog post by publicly replying to this post using a Mastodon or other ActivityPub/Fediverse account. Known non-private replies are displayed below.", language_strings=language_strings) }}</p>
<div class="dialog-buttons">
<button id="load-comments" class="inline-button">
{{- macros_translate::translate(key="load_comments", default="Load Comments", language_strings=language_strings) -}}
</button>
<a class="inline-button colored external" href="https://{{ host }}/@{{ user }}/{{ id }}" rel="{{ rel_attributes }}">
{{- macros_translate::translate(key="open_post", default="Open Post", language_strings=language_strings) -}}
</a>
</div>
<div id="comments-wrapper">
<noscript>
<p>{{ macros_translate::translate(key="comments_noscript", default="Loading comments relies on JavaScript. Try enabling JavaScript and reloading, or visit the original post on Mastodon.", language_strings=language_strings) }}</p>
</noscript>
</div>
</section>

View File

@ -20,10 +20,7 @@
{%- if link.menu and link.menu | length > 0 -%} {%- if link.menu and link.menu | length > 0 -%}
<li> <li>
<details class="closable"> <details class="closable">
<summary> <summary>{{- macros_translate::translate(key=link.name, default=link.name, language_strings=language_strings) -}}</summary>
<i class="icon arrow"></i>
{{- macros_translate::translate(key=link.name, default=link.name, language_strings=language_strings) -}}
</summary>
<ul> <ul>
{%- for link in link.menu -%} {%- for link in link.menu -%}
{%- if link.url is matching('https?://') %} {%- if link.url is matching('https?://') %}