{% macro avatar(username, size="24px", selector_type="username") -%} <img title="{{ username }}'s avatar" src="/api/v1/auth/user/{{ username }}/avatar?selector_type={{ selector_type }}" alt="@{{ username }}" class="avatar shadow" loading="lazy" style="--size: {{ size }}" /> {%- endmacro %} {% macro community_avatar(id, community=false, size="24px") -%} {% if community %} <img src="/api/v1/communities/{{ id }}/avatar" alt="{{ community.title }}'s avatar" class="avatar shadow" loading="lazy" style="--size: {{ size }}" /> {% else %} <img src="/api/v1/communities/{{ id }}/avatar" alt="{{ id }}'s avatar" class="avatar shadow" loading="lazy" style="--size: {{ size }}" /> {% endif %} {%- endmacro %} {% macro banner(username, border_radius="var(--radius)") -%} <img title="{{ username }}'s banner" src="/api/v1/auth/user/{{ username }}/banner" alt="@{{ username }}'s banner" class="banner shadow w-full" loading="lazy" style="border-radius: {{ border_radius }};" /> {%- endmacro %} {% macro community_banner(id, community=false) -%} {% if community %} <img src="/api/v1/communities/{{ id }}/banner" alt="{{ community.title }}'s banner" class="banner shadow" loading="lazy" /> {% else %} <img src="/api/v1/communities/{{ id }}/banner" alt="{{ id }}'s banner" class="banner shadow" loading="lazy" /> {% endif %} {%- endmacro %} {% macro community_listing_card(community) -%} <a class="card secondary w-full flex items-center gap-4" href="/community/{{ community.title }}" > {{ self::community_avatar(id=community.id, community=community, size="48px") }} <div class="flex flex-col"> <h3 class="name lg:long">{{ community.context.display_name }}</h3> <span class="fade"><b>{{ community.member_count }}</b> members</span> </div> </a> {%- endmacro %} {% macro username(user) -%} <div style="display: contents"> {% if user.settings.display_name %} {{ user.settings.display_name }} {% else %} {{ user.username }} {% endif %} </div> {%- endmacro %} {% macro likes(id, asset_type, likes=0, dislikes=0, secondary=false) -%} <button title="Like" class="{% if secondary %}quaternary{% else %}camo{% endif %} small" hook_element="reaction.like" onclick="trigger('me::react', [event.target, '{{ id }}', '{{ asset_type }}', true])" > {{ icon "heart" }} {% if likes > 0 %} <span>{{ likes }}</span> {% endif %} </button> {% if not user or not user.settings.hide_dislikes %} <button title="Dislike" class="{% if secondary %}quaternary{% else %}camo{% endif %} small" hook_element="reaction.dislike" onclick="trigger('me::react', [event.target, '{{ id }}', '{{ asset_type }}', false])" > {{ icon "heart-crack" }} {% if dislikes > 0 %} <span>{{ dislikes }}</span> {% endif %} </button> {% endif %} {%- endmacro %} {% macro full_username(user) -%} <div class="flex items-center"> <a href="/@{{ user.username }}" class="flush" style="font-weight: 600" target="_top" > {{ self::username(user=user) }} </a> {{ self::online_indicator(user=user) }} {% if user.is_verified %} <span title="Verified" style="color: var(--color-primary)" class="flex items-center" > {{ icon "badge-check" }} </span> {% endif %} </div> {%- endmacro %} {% macro repost(repost, post, owner, secondary=false, community=false, show_community=true, can_manage_post=false) -%} <div style="display: contents"> <!-- prettier-ignore --> <div style="display: none" id="repost-content:{{ post.id }}"> {% if repost %} {{ self::post(post=repost[1], owner=repost[0], secondary=not secondary, community=false, show_community=false, can_manage_post=false) }} {% else %} <div class="card tertiary red flex items-center gap-2"> {{ icon "frown" }} <span>Could not find original post...</span> </div> {% endif %} </div> {{ self::post(post=post, owner=owner, secondary=secondary, community=community, show_community=show_community, can_manage_post=can_manage_post) }} <script> document.getElementById("post-content:{{ post.id }}").innerHTML += document.getElementById("repost-content:{{ post.id }}").innerHTML; document.getElementById("repost-content:{{ post.id }}").remove(); document .getElementById("post:{{ post.id }}") .querySelector(".avatar") .setAttribute("style", "--size: 24px"); document .getElementById("post:{{ post.id }}") .querySelector(".name") .parentElement.prepend( document .getElementById("post:{{ post.id }}") .querySelector(".avatar"), ); </script> </div> {%- endmacro %} {% macro post(post, owner, question=false, secondary=false, community=false, show_community=true, can_manage_post=false) -%} {% if community and show_community and community.id != config.town_square or question %} <div class="card-nest"> {% if question %} {{ self::question(question=question[0], owner=question[1]) }} {% else %} <div class="card small"> <a href="/api/v1/communities/find/{{ post.community }}" class="flush flex gap-1 items-center" > {{ self::community_avatar(id=post.community, community=community) }} <b> <!-- prettier-ignore --> {% if community.context.display_name %} {{ community.context.display_name }} {% else %} {{ community.title }} {% endif %} </b> {% if post.context.is_pinned or post.context.is_profile_pinned %} {{ icon "pin" }} {% endif %} </a> </div> {% endif %} {% endif %} <div class="card flex flex-col gap-2 {% if secondary %}secondary{% endif %}" id="post:{{ post.id }}" > <div class="w-full flex gap-2"> <a href="/@{{ owner.username }}"> {{ self::avatar(username=owner.username, size="52px", selector_type="username") }} </a> <div class="flex flex-col w-full gap-1"> <div class="flex flex-wrap gap-2 items-center"> <span class="name" >{{ self::full_username(user=owner) }}</span > {% if post.context.edited != 0 %} <div class="flex"> <span class="fade date">{{ post.context.edited }}</span> <sup title="Edited">*</sup> </div> {% else %} <span class="fade date">{{ post.created }}</span> {% endif %} {% if post.context.is_nsfw %} <span title="NSFW post" class="flex items-center" style="color: var(--color-primary)" > {{ icon "square-asterisk" }} </span> {% endif %} {% if post.context.repost and post.context.repost.reposting %} <span title="Repost" class="flex items-center" style="color: var(--color-primary)" > {{ icon "repeat-2" }} </span> {% endif %} {% if post.community == config.town_square %} <span title="Posted to profile" class="flex items-center" style="color: var(--color-primary)" > {{ icon "user-round" }} </span> {% endif %} </div> {% if not post.context.content_warning %} <span id="post-content:{{ post.id }}" class="no_p_margin" >{{ post.content|markdown|safe }}</span > {% else %} <details> <summary class="card flex gap-2 items-center secondary red w-full" > {{ icon "triangle-alert" }} <b>{{ post.context.content_warning }}</b> </summary> <span id="post-content:{{ post.id }}" class="no_p_margin" >{{ post.content|markdown|safe }}</span > </details> {% endif %} </div> </div> <div class="flex justify-between items-center gap-2 w-full"> {% if user %} <div class="flex gap-1 reactions_box" hook="check_reactions" hook-arg:id="{{ post.id }}" > <!-- prettier-ignore --> {% if post.context.reactions_enabled %} {% if post.content|length > 0 %} {{ self::likes(id=post.id, asset_type="Post", likes=post.likes, dislikes=post.dislikes) }} {% endif %} {% endif %} {% if post.context.repost and post.context.repost.reposting %} <a href="/post/{{ post.context.repost.reposting }}" class="button small camo" title='{{ text "communities:label.expand_original" }}' > {{ icon "expand" }} </a> {% endif %} </div> {% else %} <div></div> {% endif %} <div class="flex gap-1 buttons_box"> <a href="/post/{{ post.id }}" class="button camo small"> {{ icon "message-circle" }} <span>{{ post.comment_count }}</span> </a> <a href="/post/{{ post.id }}" class="button camo small" target="_blank" > {{ icon "external-link" }} </a> {% if user %} <div class="dropdown"> <button class="camo small" onclick="trigger('atto::hooks::dropdown', [event])" exclude="dropdown" > {{ icon "ellipsis" }} </button> <div class="inner"> {% if config.town_square and post.context.reposts_enabled %} <b class="title">{{ text "general:label.share" }}</b> <button onclick="trigger('me::repost', ['{{ post.id }}', '', '{{ config.town_square }}'])" > {{ icon "repeat-2" }} <span>{{ text "communities:label.repost" }}</span> </button> <button onclick="window.REPOST_ID = '{{ post.id }}'; document.getElementById('quote_dialog').showModal()" > {{ icon "quote" }} <span >{{ text "communities:label.quote_post" }}</span > </button> {% endif %} {% if user.id != post.owner %} <b class="title">{{ text "general:label.safety" }}</b> <button class="red" onclick="trigger('me::report', ['{{ post.id }}', 'post'])" > {{ icon "flag" }} <span>{{ text "general:action.report" }}</span> </button> {% endif %} {% if (user.id == post.owner) or is_helper or can_manage_post %} <b class="title">{{ text "general:action.manage" }}</b> {% if user.id == post.owner %} <a href="/post/{{ post.id }}#/edit"> {{ icon "pen" }} <span >{{ text "communities:label.edit_content" }}</span > </a> {% endif %} <a href="/post/{{ post.id }}#/configure"> {{ icon "settings" }} <span >{{ text "communities:action.configure" }}</span > </a> <button class="red" onclick="trigger('me::remove_post', ['{{ post.id }}'])" > {{ icon "trash" }} <span>{{ text "general:action.delete" }}</span> </button> {% endif %} </div> </div> {% endif %} </div> </div> </div> {% if community and show_community and community.id != config.town_square or question %} </div> {% endif %} {%- endmacro %} {% macro notification(notification) -%} <div class="w-full card-nest"> <div class="card small notif_title flex items-center"> {% if not notification.read %} <svg width="24" height="24" viewBox="0 0 24 24" style="fill: var(--color-link)" > <circle cx="12" cy="12" r="6"></circle> </svg> {% endif %} <b class="no_p_margin">{{ notification.title|markdown|safe }}</b> </div> <div class="card notif_content flex flex-col gap-2"> <span class="no_p_margin" >{{ notification.content|markdown|safe }}</span > <div class="card secondary w-full flex flex-wrap gap-2"> {% if notification.read %} <button class="tertiary" onclick="trigger('me::update_notification_read_status', ['{{ notification.id }}', false])" > {{ icon "undo" }} <span>{{ text "notifs:action.mark_as_unread" }}</span> </button> {% else %} <button class="green tertiary" onclick="trigger('me::update_notification_read_status', ['{{ notification.id }}', true])" > {{ icon "check" }} <span>{{ text "notifs:action.mark_as_read" }}</span> </button> {% endif %} <button class="red tertiary" onclick="trigger('me::remove_notification', ['{{ notification.id }}'])" > {{ icon "trash" }} <span>{{ text "general:action.delete" }}</span> </button> </div> </div> </div> {%- endmacro %} {% macro user_card(user) -%} <a class="card-nest w-full" href="/@{{ user.username }}"> <div class="card small" style="padding: 0"> {{ self::banner(username=user.username, border_radius="0px") }} </div> <div class="card secondary flex items-center gap-4"> {{ self::avatar(username=user.username, size="48px") }} <div class="flex items-center"> <b>{{ self::username(user=user) }}</b> {{ self::online_indicator(user=user) }} </div> </div> </a> {%- endmacro %} {% macro pagination(page=0, items=0, key="", value="") -%} <div class="flex justify-between gap-2 w-full"> {% if page > 0 %} <a class="button quaternary" href="?page={{ page - 1 }}{{ key }}{{ value }}" > {{ icon "arrow-left" }} <span>{{ text "general:link.previous" }}</span> </a> {% else %} <div></div> {% endif %} {% if items != 0 %} <a class="button quaternary" href="?page={{ page + 1 }}{{ key }}{{ value }}" > <span>{{ text "general:link.next" }}</span> {{ icon "arrow-right"}} </a> {% endif %} </div> {%- endmacro %} {% macro online_indicator(user) -%} {% if not user.settings.private_last_seen or is_helper %} <div class="online_indicator" style="display: contents" hook="online_indicator" hook-arg:last_seen="{{ user.last_seen }}" > <div style="display: none" hook_ui_ident="online" title="Online"> <svg width="24" height="24" viewBox="0 0 24 24" style="fill: var(--color-green)" > <circle cx="12" cy="12" r="6"></circle> </svg> </div> <div style="display: none" hook_ui_ident="idle" title="Idle"> <svg width="24" height="24" viewBox="0 0 24 24" style="fill: var(--color-yellow)" > <circle cx="12" cy="12" r="6"></circle> </svg> </div> <div style="display: none" hook_ui_ident="offline" title="Offline"> <svg width="24" height="24" viewBox="0 0 24 24" style="fill: hsl(0, 0%, 50%)" > <circle cx="12" cy="12" r="6"></circle> </svg> </div> </div> {% else %} <div title="Offline" style="display: contents"> <svg width="24" height="24" viewBox="0 0 24 24" style="fill: hsl(0, 0%, 50%)" > <circle cx="12" cy="12" r="6"></circle> </svg> </div> {% endif %} {%- endmacro %} {% macro theme(user, theme_preference) -%} {% if user %} {% if user.settings.theme_hue %} <style> :root, * { --hue: {{ user.settings.theme_hue }} !important; } </style> {% endif %} {% if user.settings.theme_sat %} <style> :root, * { --sat: {{ user.settings.theme_sat }} !important; } </style> {% endif %} {% if user.settings.theme_lit %} <style> :root, * { --lit: {{ user.settings.theme_lit }} !important; } </style> {% endif %} {% if theme_preference %} <script> function match_user_theme() { const pref = "{{ theme_preference }}".toLowerCase(); if (pref === "auto") { return; } document.documentElement.className = pref; } setTimeout(() => { match_user_theme(); }, 150); </script> {% endif %} <!-- prettier-ignore --> <div style="display: none;"> {{ self::theme_color(color=user.settings.theme_color_surface, css="color-surface") }} {{ self::theme_color(color=user.settings.theme_color_text, css="color-text") }} {{ self::theme_color(color=user.settings.theme_color_text_link, css="color-link") }} {{ self::theme_color(color=user.settings.theme_color_lowered, css="color-lowered") }} {{ self::theme_color(color=user.settings.theme_color_text_lowered, css="color-text-lowered") }} {{ self::theme_color(color=user.settings.theme_color_super_lowered, css="color-super-lowered") }} {{ self::theme_color(color=user.settings.theme_color_raised, css="color-raised") }} {{ self::theme_color(color=user.settings.theme_color_text_raised, css="color-text-raised") }} {{ self::theme_color(color=user.settings.theme_color_super_raised, css="color-super-raised") }} {{ self::theme_color(color=user.settings.theme_color_primary, css="color-primary") }} {{ self::theme_color(color=user.settings.theme_color_text_primary, css="color-text-primary") }} {{ self::theme_color(color=user.settings.theme_color_primary_lowered, css="color-primary-lowered") }} {{ self::theme_color(color=user.settings.theme_color_secondary, css="color-secondary") }} {{ self::theme_color(color=user.settings.theme_color_text_secondary, css="color-text-secondary") }} {{ self::theme_color(color=user.settings.theme_color_secondary_lowered, css="color-secondary-lowered") }} {% if user.permissions|has_supporter %} <style>{{ user.settings.theme_custom_css }}</style> {% endif %} </div> {% endif %} {%- endmacro %} {% macro theme_color(color, css) -%} {% if color %} <!-- prettier-ignore --> <style> :root, * { --{{ css }}: {{ color|color }} !important; } </style> {% endif %} {%- endmacro %} {% macro quote_form() -%} {% if config.town_square and user %} <div class="card-nest"> <div class="card small flex flex-col"> <div class="flex items-center gap-2"> {{ icon "quote" }} <span>{{ text "communities:label.quote_post" }}</span> </div> </div> <form class="card flex flex-col gap-2" onsubmit="create_repost_from_form(event)" > <div class="flex flex-col gap-1"> <label for="content">{{ text "communities:label.content" }}</label> <textarea type="text" name="content" id="content" placeholder="content" required minlength="2" maxlength="4096" ></textarea> </div> <button class="primary">{{ text "communities:action.create" }}</button> </form> </div> <script> async function create_repost_from_form(e) { e.preventDefault(); await trigger("atto::debounce", ["posts::create"]); await trigger("me::repost", [ window.REPOST_ID, e.target.content.value, "{{ config.town_square }}", ]); } </script> {% endif %} {%- endmacro %} {% macro question(question, owner, show_community=true, secondary=false) -%} <div class="card{% if secondary %} secondary{% endif %} flex gap-2"> {% if owner.id == 0 %} <span> {% if profile and profile.settings.anonymous_avatar_url %} <img src="/api/v1/util/proxy?url={{ profile.settings.anonymous_avatar_url }}" alt="anonymous' avatar" class="avatar shadow" loading="lazy" style="--size: 52px" /> {% else %} {{ self::avatar(username=owner.username, selector_type="username", size="52px") }} {% endif %} </span> {% else %} <a href="/@{{ owner.username }}"> {{ self::avatar(username=owner.username, selector_type="username", size="52px") }} </a> {% endif %} <div class="flex flex-col gap-1"> <div class="flex items-center gap-2 flex-wrap"> <!-- prettier-ignore --> <span class="name"> {% if owner.id == 0 %} {% if profile and profile.settings.anonymous_username %} <span class="flex items-center gap-2"> <b>{{ profile.settings.anonymous_username }}</b> <span title="Anonymous user" class="flex items-center" style="color: var(--color-primary)" > {{ icon "drama" }} </span> </span> {% else %} <b>anonymous</b> {% endif %} {% else %} {{ self::full_username(user=owner) }} {% endif %} </span> <span class="date">{{ question.created }}</span> <span title="Question" class="flex items-center" style="color: var(--color-primary)" > {{ icon "message-circle-heart" }} </span> {% if question.context.is_nsfw %} <span title="NSFW community" class="flex items-center" style="color: var(--color-primary)" > {{ icon "square-asterisk" }} </span> {% endif %} {% if question.community > 0 and show_community %} <a href="/api/v1/communities/find/{{ question.community }}" class="flex items-center" > {{ self::community_avatar(id=question.community, size="24px") }} </a> {% endif %} {% if question.is_global %} <a class="notification chip" href="/question/{{ question.id }}" >{{ question.answer_count }} answers</a > {% endif %} </div> <span class="no_p_margin" style="font-weight: 500" >{{ question.content|markdown|safe }}</span > <div class="flex gap-2 items-center justify-between"></div> </div> </div> {%- endmacro %} {% macro create_question_form(receiver="0", community="", header="", is_global=false) -%} <div class="card-nest"> <div class="card small flex items-center gap-2"> {{ icon "message-circle-heart" }} <span class="no_p_margin"> <!-- prettier-ignore --> {% if header %} {{ header|markdown|safe }} {% else %} {{ text "requests:label.ask_question" }} {% endif %} </span> </div> <form class="card flex flex-col gap-2" onsubmit="create_question_from_form(event)" > <div class="flex flex-col gap-1"> <label for="content">{{ text "communities:label.content" }}</label> <textarea type="text" name="content" id="content" placeholder="content" required minlength="2" maxlength="4096" ></textarea> </div> <button class="primary">{{ text "communities:action.create" }}</button> </form> </div> <script> async function create_question_from_form(e) { e.preventDefault(); await trigger("atto::debounce", ["questions::create"]); fetch("/api/v1/questions", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ content: e.target.content.value, receiver: "{{ receiver }}", community: "{{ community }}", is_global: "{{ is_global }}" == "true", }), }) .then((res) => res.json()) .then((res) => { trigger("atto::toast", [ res.ok ? "success" : "error", res.message, ]); if (res.ok) { e.target.reset(); } }); } </script> {%- endmacro %} {% macro global_question(question, can_manage_questions=false, secondary=false, show_community=true) -%} <div class="card-nest"> {{ self::question(question=question[0], owner=question[1], show_community=show_community) }} <div class="small card flex justify-between flex-wrap gap-2{% if secondary %} secondary{% endif %}" > <div class="flex gap-1 reactions_box" hook="check_reactions" hook-arg:id="{{ question[0].id }}" > {{ self::likes(id=question[0].id, asset_type="Question", likes=question[0].likes, dislikes=question[0].dislikes, secondary=false) }} </div> <div class="flex gap-1 buttons_box"> <a href="/question/{{ question[0].id }}" class="button small"> {{ icon "external-link" }} {% if user %} <span>{{ text "requests:label.answer" }}</span> {% else %} <span>{{ text "general:action.open" }}</span> {% endif %} </a> {% if user %} {% if can_manage_questions or is_helper or question[1].id == user.id %} <div class="dropdown"> <button class="camo small" onclick="trigger('atto::hooks::dropdown', [event])" exclude="dropdown" > {{ icon "ellipsis" }} </button> <div class="inner"> <button class="camo small red" onclick="trigger('me::remove_question', ['{{ question[0].id }}'])" > {{ icon "trash" }} <span>{{ text "general:action.delete" }}</span> </button> </div> </div> {% endif %} {% endif %} </div> </div> </div> {%- endmacro %} {% macro spotify_playing(state, size="60px") -%} {% if state and state.data %} <div class="card-nest"> <div class="card flex items-center justify-between gap-2 small"> <div class="flex items-center gap-2"> <b>Listening on</b> {{ icon "spotify" }} </div> <span class="fade date short">{{ state.data.timestamp }}</span> </div> <div class="card secondary flex gap-2"> <a href="{{ state.external_urls.album }}"> <img src="{{ state.external_urls.album_img }}" alt="Album cover" loading="lazy" class="avatar" style="--size: {{ size }}" /> </a> <div class="flex flex-col"> <h5 class="w-full"> <a href="{{ state.external_urls.track }}" class="flush" >{{ state.data.track }}</a > </h5> <span class="fade" ><a href="{{ state.external_urls.artist }}" class="flush" >{{ state.data.artist }}</a ></span > <span hook="spotify_time_text" hook-arg:updated="{{ state.data.timestamp }}" hook-arg:progress="{{ state.data.progress_ms }}" hook-arg:duration="{{ state.data.duration_ms }}" hook-arg:display="full" ></span> </div> </div> </div> {% endif %} {%- endmacro %} {% macro last_fm_playing(state, size="60px") -%} {% if state and state.data %} <div class="card-nest"> <div class="card flex items-center justify-between gap-2 small"> <div class="flex items-center gap-2"> <b>Listening on</b> {{ icon "last_fm" }} </div> <span class="fade date short">{{ state.data.timestamp }}</span> </div> <div class="card secondary flex gap-2"> <a href="{{ state.external_urls.track }}"> <img src="{{ state.external_urls.track_img }}" alt="Track cover" loading="lazy" class="avatar" style="--size: {{ size }}" /> </a> <div class="flex flex-col"> <h5 class="w-full"> <a href="{{ state.external_urls.track }}" class="flush" >{{ state.data.track }}</a > </h5> <span class="fade" ><a href="{{ state.external_urls.artist }}" class="flush" >{{ state.data.artist }}</a ></span > {% if state.data.duration_ms and state.data.duration_ms != "0" %} <span hook="spotify_time_text" hook-arg:updated="{{ state.data.timestamp }}" hook-arg:progress="25000" hook-arg:duration="{{ state.data.duration_ms }}" hook-arg:display="full" ></span> {% endif %} </div> </div> </div> {% endif %} {%- endmacro %} {% macro connection_icon(key) -%} <!-- prettier-ignore --> <div style="display: contents;"> {% if key == "Spotify" %} {{ icon "spotify" }} {% elif key == "LastFm" %} {{ icon "last_fm" }} {% endif %} </div> {%- endmacro %} {% macro connection_url(key, value) -%} {% if value[0].data.url %} {{ value[0].data.url }} {% elif key == "LastFm" %} https://last.fm/user/{{ value[0].data.name }} {% endif %} {%- endmacro %} {% macro message_actions(can_manage_message, user, message) -%} <div class="dropdown"> <button class="camo small" onclick="trigger('atto::hooks::dropdown', [event])" exclude="dropdown" > {{ icon "ellipsis" }} </button> <div class="inner"> {% if can_manage_message or (user and user.id == message.owner) %} <button class="red" onclick="delete_message('{{ message.id }}')"> {{ icon "trash" }} <span>{{ text "general:action.delete" }}</span> </button> {% endif %} <button onclick="window.location.href = `${window.location.origin}/chats/{{ community }}/{{ channel }}?message={{ message.id }}`" > {{ icon "external-link" }} <span>{{ text "general:action.open" }}</span> </button> <button onclick="trigger('atto::copy_text', [`${window.location.origin}/chats/{{ community }}/{{ channel }}?message={{ message.id }}`])" > {{ icon "copy" }} <span>{{ text "general:action.copy_link" }}</span> </button> </div> </div> {%- endmacro %} {% macro message(user, message, can_manage_message=false, grouped=false) -%} <div class="card secondary message flex gap-2 {% if grouped %}grouped{% endif %}" id="message-{{ message.id }}" > {% if not grouped %} <a href="/@{{ user.username }}" target="_top"> {{ self::avatar(username=user.username, size="52px") }} </a> {% endif %} <div class="flex flex-col gap-1 w-full"> {% if not grouped %} <div class="flex gap-2 w-full justify-between flex-wrap"> <div class="flex gap-2"> {{ self::full_username(user=user) }} {% if message.edited != message.created %} <span class="date" >{{ message.edited }}<sup title="Edited">*</sup></span > {% else %} <span class="date">{{ message.created }}</span> {% endif %} </div> <div class="flex gap-2 hidden"> {{ self::message_actions(user=user, message=message, can_manage_message=can_manage_message) }} </div> </div> {% endif %} <div class="flex w-full gap-2 justify-between"> <span class="no_p_margin">{{ message.content|markdown|safe }}</span> {% if grouped %} <div class="hidden"> {{ self::message_actions(user=user, message=message, can_manage_message=can_manage_message) }} </div> {% endif %} </div> </div> </div> {%- endmacro %} {% macro user_menu() -%} <div class="inner"> <b class="title">{{ user.username }}</b> <a href="/@{{ user.username }}"> {{ icon "circle-user-round" }} <span>{{ text "auth:link.my_profile" }}</span> </a> <a href="/settings"> {{ icon "settings" }} <span>{{ text "auth:link.settings" }}</span> </a> {% if is_helper %} <b class="title">{{ text "general:label.mod" }}</b> <a href="/mod_panel/audit_log"> {{ icon "scroll-text" }} <span>{{ text "general:link.audit_log" }}</span> </a> <a href="/mod_panel/reports"> {{ icon "flag" }} <span>{{ text "general:link.reports" }}</span> </a> <a href="/mod_panel/ip_bans"> {{ icon "ban" }} <span>{{ text "general:link.ip_bans" }}</span> </a> <a href="/mod_panel/stats"> {{ icon "chart-line" }} <span>{{ text "general:link.stats" }}</span> </a> {% endif %} <b class="title">{{ config.name }}</b> <a href="https://trisua.com/t/tetratto"> {{ icon "code" }} <span>{{ text "general:link.source_code" }}</span> </a> <!-- <a href="https://trisuaso.github.io/tetratto"> {{ icon "book" }} <span>{{ text "general:link.reference" }}</span> </a> --> <div class="title"></div> <button onclick="trigger('me::switch_account')"> {{ icon "ellipsis" }} <span>{{ text "general:action.switch_account" }}</span> </button> <button class="red" onclick="trigger('me::logout')"> {{ icon "log-out" }} <span>{{ text "auth:action.logout" }}</span> </button> </div> {%- endmacro %} {% macro user_status(other_user) -%} {% if other_user.settings.status %} <div class="flex items-center gap-2"> <span>{{ other_user.settings.status }}</span> <!-- connection icon --> {% if (other_user.connections.LastFm[1].data and other_user.connections.LastFm[1].data.track) or (other_user.connections.Spotify[1].data and other_user.connections.Spotify[1].data.track) %} {{ icon "music" }} {% endif %} </div> {% elif other_user.connections.LastFm[0].data.name and other_user.connections.LastFm[1].data and other_user.connections.LastFm[1].data.track %} <div class="flex items-center gap-2"> {{ icon "music" }} <span ><b>Listening to</b> {{ other_user.connections.LastFm[1].data.artist }}</span > </div> {% elif other_user.connections.Spotify[0].data.name and other_user.connections.Spotify[1].data and other_user.connections.Spotify[1].data.track %} <div class="flex items-center gap-2"> {{ icon "music" }} <span ><b>Listening to</b> {{ other_user.connections.Spotify[1].data.artist }}</span > </div> {% endif %} {%- endmacro %} {% macro user_plate(user, show_menu=false, secondary=false) -%} <div class="flex gap-2 items-center card tiny user_plate {% if secondary %}secondary{% endif %}" > <a href="/@{{ user.username }}"> {{ self::avatar(username=user.username, size="42px", selector_type="username") }} </a> <div class="flex justify-center flex-col" style="{% if show_menu %}width: 60%{% endif %}" > {{ self::full_username(user=user) }} <div class="user_status">{{ self::user_status(other_user=user) }}</div> </div> {% if show_menu %} <div class="dropdown"> <button class="camo small square" onclick="trigger('atto::hooks::dropdown', [event])" exclude="dropdown" > {{ icon "settings" c(dropdown-arrow) }} </button> {{ self::user_menu() }} </div> {% endif %} </div> {%- endmacro %} {% macro emoji_picker(element_id, render_dialog=false) -%} <button class="button small square quaternary" onclick="window.EMOJI_PICKER_TEXT_ID = '{{ element_id }}'; document.getElementById('emoji_dialog').showModal()" title="Emojis" type="button" > {{ icon "smile-plus" }} </button> {% if render_dialog %} <dialog id="emoji_dialog"> <div class="inner flex flex-col gap-2"> <script type="module" src="https://unpkg.com/emoji-picker-element@1.22.8/index.js" ></script> <emoji-picker style=" --border-radius: var(--radius); --background: var(--color-super-raised); --input-border-radiFus: var(--radius); --input-border-color: var(--color-primary); --indicator-color: var(--color-primary); --emoji-padding: 0.25rem; box-shadow: 0 0 4px var(--color-shadow); " class="w-full" ></emoji-picker> <script> document .querySelector("emoji-picker") .addEventListener("emoji-click", async (event) => { if (event.detail.skinTone > 0) { document.getElementById( window.EMOJI_PICKER_TEXT_ID, ).value += event.detail.unicode; document.getElementById("emoji_dialog").close(); return; } document.getElementById( window.EMOJI_PICKER_TEXT_ID, ).value += ` :${await ( await fetch("/api/v1/lookup_emoji", { method: "POST", body: event.detail.unicode, }) ).text()}:`; document.getElementById("emoji_dialog").close(); }); </script> <div class="flex justify-between"> <div></div> <div class="flex gap-2"> <button class="bold red quaternary" onclick="document.getElementById('emoji_dialog').close()" type="button" > {{ icon "x" }} {{ text "dialog:action.close" }} </button> </div> </div> </div> </dialog> {% endif %} {%- endmacro %}