tetratto/crates/app/src/public/html/components.lisp
2025-06-27 14:21:42 -04:00

2261 lines
90 KiB
Common Lisp

(text "{% 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 }}"))
(text "{%- 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 }}"))
(text "{% else %}")
(img
("src" "/api/v1/communities/{{ id }}/avatar")
("alt" "{{ id }}'s avatar")
("class" "avatar shadow")
("loading" "lazy")
("style" "--size: {{ size }}"))
(text "{%- 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 }};"))
(text "{%- 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"))
(text "{% else %}")
(img
("src" "/api/v1/communities/{{ id }}/banner")
("alt" "{{ id }}'s banner")
("class" "banner shadow")
("loading" "lazy"))
(text "{%- endif %} {%- endmacro %} {% macro community_listing_card(community) -%}")
(a
("class" "card secondary w-full flex items-center gap-4")
("href" "{% if community.is_forge -%}/forge/{{ community.title }}{% else %}/community/{{ community.title }}{%- endif %}")
(text "{{ self::community_avatar(id=community.id, community=community, size=\"48px\") }}")
(div
("class" "flex flex-col")
(div
("class" "flex gap-2 items-center")
(text "{% if community.is_forge -%}")
(icon (text "anvil"))
(text "{%- endif %}")
(h3
("class" "name lg:long")
(text "{{ community.context.display_name }}")))
(span
("class" "fade")
(b
(text "{{ community.member_count }} "))
(text "members"))))
(text "{%- endmacro %} {% macro username(user) -%}")
(div
("style" "display: contents")
(text "{% if user.settings.display_name -%} {{ user.settings.display_name }} {% else %} {{ user.username }} {%- endif %}"))
(text "{%- endmacro %} {% macro likes(id, asset_type, likes=0, dislikes=0, secondary=false, disable_dislikes=false) -%}")
(button
("title" "Like")
("class" "{% if secondary -%}lowered{% else %}camo{%- endif %} small")
("hook_element" "reaction.like")
("onclick" "trigger('me::react', [event.target, '{{ id }}', '{{ asset_type }}', true])")
(text "{{ icon \"heart\" }} {% if likes > 0 -%}")
(span
(text "{{ likes }}"))
(text "{%- endif %}"))
(text "{% if not user or not user.settings.hide_dislikes and not disable_dislikes -%}")
(button
("title" "Dislike")
("class" "{% if secondary -%}lowered{% else %}camo{%- endif %} small")
("hook_element" "reaction.dislike")
("onclick" "trigger('me::react', [event.target, '{{ id }}', '{{ asset_type }}', false])")
(text "{{ icon \"heart-crack\" }} {% if dislikes > 0 -%}")
(span
(text "{{ dislikes }}"))
(text "{%- endif %}"))
(text "{%- endif %} {%- endmacro %} {% macro full_username(user) -%} {% if user and user.username -%}")
(div
("class" "flex items-center")
(a
("href" "/@{{ user.username }}")
("class" "flush")
("style" "font-weight: 600")
("target" "_top")
(text "{{ self::username(user=user) }}"))
(text "{{ self::online_indicator(user=user) }} {% if user.is_verified -%}")
(span
("title" "Verified")
("style" "color: var(--color-primary)")
("class" "flex items-center")
(text "{{ icon \"badge-check\" }}"))
(text "{%- endif %}"))
(text "{%- endif %} {%- endmacro %} {% macro repost(repost, post, owner, secondary=false, community=false, show_community=true, can_manage_post=false) -%}")
(div
("style" "display: contents")
(text "{{ self::post(post=post, owner=owner, secondary=secondary, community=community, show_community=show_community, can_manage_post=can_manage_post, repost=repost, expect_repost=true) }}"))
(text "{%- endmacro %} {% macro post(post, owner, question=false, secondary=false, community=false, show_community=true, can_manage_post=false, repost=false, expect_repost=false, poll=false, dont_show_title=false, is_repost=false) -%} {% if community and show_community and community.id != config.town_square or question %}")
(div
("class" "card-nest post_outer:{{ post.id }} post_outer")
("is_repost" "{{ is_repost }}")
(text "{% if question -%} {{ self::question(question=question[0], owner=question[1], profile=owner) }} {% else %}")
(div
("class" "card small")
(a
("href" "/api/v1/communities/find/{{ post.community }}")
("class" "flush flex gap-1 items-center")
(text "{{ self::community_avatar(id=post.community, community=community) }}")
(b
(text "{% if community.context.display_name -%} {{ community.context.display_name }} {% else %} {{ community.title }} {%- endif %}"))
(text "{% if post.context.is_pinned or post.context.is_profile_pinned -%} {{ icon \"pin\" }} {%- endif %}")))
(text "{%- endif %} {%- endif %}")
(div
("class" "card flex flex-col post gap-2 post:{{ post.id }} {% if secondary -%}secondary{%- endif %}")
("data-community" "{{ post.community }}")
("data-ownsup" "{{ owner.permissions|has_supporter }}")
("data-id" "{{ post.id }}")
("hook" "verify_emojis")
(div
("class" "w-full flex gap-2")
(text "{% if not expect_repost -%}")
(a
("href" "/@{{ owner.username }}")
(text "{{ self::avatar(username=owner.username, size=\"52px\", selector_type=\"username\") }}"))
(text "{%- endif %}")
(div
("class" "flex flex-col w-full gap-1 post_right {% if expect_repost -%}repost{%- endif %}")
(div
("class" "flex flex-wrap gap-2 items-center")
(text "{% if expect_repost -%}")
(a
("href" "/@{{ owner.username }}")
(text "{{ self::avatar(username=owner.username, size=\"24px\", selector_type=\"username\") }}"))
(text "{%- endif %}")
(span
("class" "name")
(text "{{ self::full_username(user=owner) }}"))
(text "{% if post.context.edited != 0 -%}")
(div
("class" "flex")
(span
("class" "fade date")
(text "{{ post.context.edited }}"))
(sup
("title" "Edited")
(text "*")))
(text "{% else %}")
(span
("class" "fade date")
(text "{{ post.created }}"))
(text "{%- endif %} {% if post.context.is_nsfw -%}")
(span
("title" "NSFW post")
("class" "flex items-center")
("style" "color: var(--color-primary)")
(text "{{ icon \"square-asterisk\" }}"))
(text "{%- endif %} {% if post.stack -%}")
(a
("title" "Posted to a stack you're in")
("class" "flex items-center flush")
("style" "color: var(--color-primary)")
("href" "/stacks/{{ post.stack }}")
(text "{{ icon \"layers\" }}"))
(text "{%- endif %} {% if community and community.is_forge -%} {% if post.is_open -%}")
(span
("title" "Open")
("class" "flex items-center green")
(text "{{ icon \"circle-dot\" }}"))
(text "{% else %}")
(span
("title" "Closed")
("class" "flex items-center purple")
(text "{{ icon \"circle-check\" }}"))
(text "{%- endif %} {%- endif %}")
(text "{% if post.context.repost and post.context.repost.reposting %}")
(span
("title" "Repost")
("class" "flex items-center")
("style" "color: var(--color-primary)")
(text "{{ icon \"repeat-2\" }}"))
(text "{%- endif %} {% if post.community == config.town_square -%}")
(span
("title" "Posted to profile")
("class" "flex items-center")
("style" "color: var(--color-primary)")
(text "{{ icon \"user-round\" }}"))
(text "{%- endif %} {% if post.is_deleted -%}")
(span
("title" "Deleted")
("class" "flex items-center")
("style" "color: var(--color-primary)")
(text "{{ icon \"trash-2\" }}"))
(text "{%- endif %}"))
(text "{% if not dont_show_title and post.title and community and community.context.enable_titles -%}")
; post has a title AND whatever is rendering this component wants to see it
(a
("class" "flush")
("href" "/post/{{ post.id }}")
(h2
("id" "post_content:{{ post.id }}")
("class" "no_p_margin post_content")
("hook" "long")
(text "{{ post.title }}"))
(button ("class" "small lowered") (icon (text "ellipsis"))))
(text "{% else %}")
(text "{% if not post.context.content_warning -%}")
(span
("class" "no_p_margin post_content")
("hook" "long")
; title
(text "{% if post.title and community and community.context.enable_titles -%}")
(h2 (text "{{ post.title }}"))
(hr ("class" "margin") ("style" "margin-top: var(--pad-2)"))
(text "{%- endif %}")
; content
(span ("id" "post_content:{{ post.id }}") (text "{{ post.content|markdown|safe }}"))
(text "{% if expect_repost -%} {% if repost -%} {{ self::post(post=repost[1], owner=repost[0], secondary=not secondary, community=false, show_community=false, can_manage_post=false, is_repost=true) }} {% else %}")
(div
("class" "card lowered red flex items-center gap-2")
(text "{{ icon \"frown\" }}")
(span
(str (text "general:label.could_not_find_post"))))
(text "{%- endif %} {%- endif %}"))
(text "{{ self::post_media(upload_ids=post.uploads) }} {% else %}")
(details
("class" "card tiny lowered w-full")
(summary
("class" "red w-full")
(b
(text "{{ post.context.content_warning }}")))
(div
("class" "flex flex-col gap-2")
(span
("class" "no_p_margin post_content")
("hook" "long")
; title
(text "{% if post.title and community and community.settings.enable_titles %}")
(h2 (text "{{ post.title }}"))
(text "{% endif %}")
; content
(span ("id" "post_content:{{ post.id }}") (text "{{ post.content|markdown|safe }}"))
(text "{% if expect_repost -%} {% 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 lowered red flex items-center gap-2")
(text "{{ icon \"frown\" }}")
(span
(text "Could not find original post...")))
(text "{%- endif %} {%- endif %}"))
(text "{{ self::post_media(upload_ids=post.uploads) }}")))
(text "{%- endif %} {%- endif %}")
(text "{% if poll -%} {{ self::poll(post=post, poll=poll) }} {%- endif %}")
(div
("class" "flex flex-wrap gap-2 fade")
(text "{% for tag in post.context.tags %}")
(a
("href" "/@{{ owner.username }}?tag={{ tag }}")
("class" "flush fade")
(text "#{{ tag }}"))
(text "{% endfor %}"))))
(div
("class" "flex justify-between items-center gap-2 w-full")
(text "{% if user -%}")
(div
("class" "flex gap-1 reactions_box")
("hook" "check_reactions")
("hook-arg:id" "{{ post.id }}")
(text "{% if post.context.reactions_enabled -%} {% if post.content|length > 0 or post.uploads|length > 0 -%} {{ self::likes(id=post.id, asset_type=\"Post\", likes=post.likes, dislikes=post.dislikes, disable_dislikes=owner.settings.hide_dislikes) }} {%- endif %} {%- endif %} {% if post.context.repost and post.context.repost.reposting -%}")
(a
("href" "/post/{{ post.context.repost.reposting }}")
("class" "button small camo")
(text "{{ icon \"expand\" }}"))
(text "{%- endif %}"))
(text "{% else %}")
(div)
(text "{%- endif %}")
(div
("class" "flex gap-1 buttons_box")
(text "{% if post.context.comments_enabled %}")
(a
("href" "/post/{{ post.id }}")
("class" "button camo small")
(text "{{ icon \"message-circle\" }}")
(span
(text "{{ post.comment_count }}")))
(text "{% endif %}")
(a
("href" "/post/{{ post.id }}")
("class" "button camo small")
("target" "_blank")
(text "{{ icon \"external-link\" }}"))
(text "{% if user -%}")
(div
("class" "dropdown")
(button
("class" "camo small")
("onclick" "trigger('atto::hooks::dropdown', [event])")
("exclude" "dropdown")
(text "{{ icon \"ellipsis\" }}"))
(div
("class" "inner")
(text "{% if config.town_square and post.context.reposts_enabled %}")
(b
("class" "title")
(text "{{ text \"general:label.share\" }}"))
(button
("onclick" "trigger('me::repost', ['{{ post.id }}', '', '{{ config.town_square }}', true])")
(text "{{ icon \"repeat-2\" }}")
(span
(text "{{ text \"communities:label.repost\" }}")))
(a
("class" "button")
("href" "/communities/intents/post?quote={{ post.id }}")
(text "{{ icon \"quote\" }}")
(span
(text "{{ text \"communities:label.quote_post\" }}")))
(button
("onclick" "trigger('me::intent_twitter', [trigger('me::gen_share', [{ q: '{{ post.context.answering }}', p: '{{ post.id }}' }, 280, true])])")
(icon (text "bird"))
(span
(text "Twitter")))
(button
("onclick" "trigger('me::intent_bluesky', [trigger('me::gen_share', [{ q: '{{ post.context.answering }}', p: '{{ post.id }}' }, 280, true])])")
(icon (text "cloud"))
(span
(text "BlueSky")))
(text "{%- endif %}")
(text "{% if user.id != post.owner -%}")
(b
("class" "title")
(text "{{ text \"general:label.safety\" }}"))
(button
("class" "red")
("onclick" "trigger('me::report', ['{{ post.id }}', 'post'])")
(text "{{ icon \"flag\" }}")
(span
(text "{{ text \"general:action.report\" }}")))
(text "{%- endif %} {% if (user.id == post.owner) or is_helper or can_manage_post %}")
(b
("class" "title")
(text "{{ text \"general:action.manage\" }}"))
; forge stuff
(text "{% if community and community.is_forge -%} {% if post.is_open -%}")
(button
("class" "green")
("onclick" "trigger('me::update_open', ['{{ post.id }}', false])")
(text "{{ icon \"circle-check\" }}")
(span
(text "{{ text \"forge:action.close\" }}")))
(text "{% else %}")
(button
("class" "purple")
("onclick" "trigger('me::update_open', ['{{ post.id }}', true])")
(text "{{ icon \"refresh-ccw-dot\" }}")
(span
(text "{{ text \"forge:action.reopen\" }}")))
(text "{%- endif %} {%- endif %}")
; owner stuff
(text "{% if user.id == post.owner -%}")
(a
("href" "/post/{{ post.id }}#/edit")
(text "{{ icon \"pen\" }}")
(span
(text "{{ text \"communities:label.edit_content\" }}")))
(text "{%- endif %}")
(a
("href" "/post/{{ post.id }}#/configure")
(text "{{ icon \"settings\" }}")
(span
(text "{{ text \"communities:action.configure\" }}")))
(text "{% if not post.is_deleted -%}")
(button
("class" "red")
("onclick" "trigger('me::remove_post', ['{{ post.id }}'])")
(text "{{ icon \"trash\" }}")
(span
(text "{{ text \"general:action.delete\" }}")))
(text "{%- endif %} {% if is_helper and post.is_deleted -%}")
(button
("class" "red")
("onclick" "trigger('me::purge_post', ['{{ post.id }}'])")
(text "{{ icon \"trash-2\" }}")
(span
(text "{{ text \"general:action.purge\" }}")))
(button
("class" "green")
("onclick" "trigger('me::restore_post', ['{{ post.id }}'])")
(text "{{ icon \"undo\" }}")
(span
(text "{{ text \"general:action.restore\" }}")))
(text "{%- endif %} {%- endif %}")))
(text "{%- endif %}"))))
(text "{% if community and show_community and community.id != config.town_square or question %}"))
(text "{%- endif %} {%- endmacro %} {% macro post_media(upload_ids) -%} {% if upload_ids|length > 0 -%}")
(div
("class" "media_gallery gap-2")
(text "{% for upload in upload_ids %}")
(img
("src" "/api/v1/uploads/{{ upload }}")
("alt" "Image upload")
("onclick" "trigger('ui::lightbox_open', ['/api/v1/uploads/{{ upload }}'])"))
(text "{% endfor %}"))
(text "{%- endif %} {%- endmacro %} {% macro notification(notification) -%}")
(div
("class" "w-full card-nest")
(div
("class" "card small notif_title flex items-center")
(text "{% 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")))
(text "{%- endif %}")
(b
("class" "no_p_margin")
(text "{{ notification.title|markdown|safe }}")))
(div
("class" "card notif_content flex flex-col gap-2")
(span
("class" "no_p_margin")
(text "{{ notification.content|markdown|safe }}"))
(div
("class" "card secondary w-full flex flex-wrap gap-2")
(text "{% if notification.read -%}")
(button
("class" "raised")
("onclick" "trigger('me::update_notification_read_status', ['{{ notification.id }}', false])")
(text "{{ icon \"undo\" }}")
(span
(text "{{ text \"notifs:action.mark_as_unread\" }}")))
(text "{% else %}")
(button
("class" "green raised")
("onclick" "trigger('me::update_notification_read_status', ['{{ notification.id }}', true])")
(text "{{ icon \"check\" }}")
(span
(text "{{ text \"notifs:action.mark_as_read\" }}")))
(text "{%- endif %}")
(button
("class" "red raised")
("onclick" "trigger('me::remove_notification', ['{{ notification.id }}'])")
(text "{{ icon \"trash\" }}")
(span
(text "{{ text \"general:action.delete\" }}"))))))
(text "{%- endmacro %} {% macro user_card(user) -%}")
(a
("class" "card-nest w-full")
("href" "/@{{ user.username }}")
(div
("class" "card small")
("style" "padding: 0")
(text "{{ self::banner(username=user.username, border_radius=\"0px\") }}"))
(div
("class" "card secondary flex items-center gap-4")
(text "{{ self::avatar(username=user.username, size=\"48px\") }}")
(div
("class" "flex items-center")
(b
(text "{{ self::username(user=user) }}"))
(text "{{ self::online_indicator(user=user) }}"))))
(text "{%- endmacro %} {% macro pagination(page=0, items=0, key=\"\", value=\"\") -%}")
(div
("class" "flex justify-between gap-2 w-full")
(text "{% if page > 0 -%}")
(a
("class" "button lowered")
("href" "?page={{ page - 1 }}{{ key }}{{ value }}")
(text "{{ icon \"arrow-left\" }}")
(span
(text "{{ text \"general:link.previous\" }}")))
(text "{% else %}")
(div)
(text "{%- endif %} {% if items != 0 -%}")
(a
("class" "button lowered")
("href" "?page={{ page + 1 }}{{ key }}{{ value }}")
(span
(text "{{ text \"general:link.next\" }}"))
(text "{{ icon \"arrow-right\" }}"))
(text "{%- endif %}"))
(text "{%- 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"))))
(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"))))
(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")))))
(text "{% 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"))))
(text "{%- endif %} {%- endmacro %} {% macro theme(user, theme_preference) -%} {% if user %} {% if user.settings.theme_hue -%}")
(style
(text ":root, * {
--hue: {{ user.settings.theme_hue }} !important;
}"))
(text "{%- endif %} {% if user.settings.theme_sat -%}")
(style
(text ":root, * {
--sat: {{ user.settings.theme_sat }} !important;
}"))
(text "{%- endif %} {% if user.settings.theme_lit -%}")
(style
(text ":root, * {
--lit: {{ user.settings.theme_lit }} !important;
}"))
(text "{%- endif %} {% if theme_preference -%}")
(script
(text "function match_user_theme() {
const pref = \"{{ theme_preference }}\".toLowerCase();
if (pref === \"auto\") {
return;
}
document.documentElement.className = pref;
}
setTimeout(() => {
match_user_theme();
}, 150);"))
(text "{%- endif %}")
(div
("style" "display: none;")
(text "{{ 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
(text "{{ user.settings.theme_custom_css|remove_script_tags|safe }}"))
(text "{%- endif %}"))
(text "{%- endif %} {%- endmacro %} {% macro theme_color(color, css) -%} {% if color -%}")
(style
(text ":root,
* {
--{{ css }}: {{ color|color }} !important;
}"))
(text "{%- endif %} {%- endmacro %} {% macro question(question, owner, show_community=true, secondary=false, profile=false) -%}")
(div
("class" "card question {% if secondary -%}secondary{%- endif %} flex gap-2")
(text "{% if owner.id == 0 -%}")
(span
(text "{% 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"))
(text "{% else %} {{ self::avatar(username=owner.username, selector_type=\"username\", size=\"52px\") }} {%- endif %}"))
(text "{% else %}")
(a
("href" "/@{{ owner.username }}")
(text "{{ self::avatar(username=owner.username, selector_type=\"username\", size=\"52px\") }}"))
(text "{%- endif %}")
(div
("class" "flex flex-col gap-1 w-full")
(div
("class" "flex items-center gap-2 flex-wrap")
(span
("class" "name")
(text "{% if owner.id == 0 -%} {% if profile and profile.settings.anonymous_username -%}")
(span
("class" "flex items-center gap-2")
(b
(text "{{ profile.settings.anonymous_username }}"))
(span
("title" "Anonymous user")
("class" "flex items-center")
("style" "color: var(--color-primary)")
(text "{{ icon \"drama\" }}")))
(text "{% else %}")
(b
(text "anonymous"))
(text "{%- endif %} {% else %} {{ self::full_username(user=owner) }} {%- endif %}"))
(span
("class" "date")
(text "{{ question.created }}"))
(span
("title" "Question")
("class" "flex items-center")
("style" "color: var(--color-primary)")
(text "{{ icon \"message-circle-heart\" }}"))
(text "{% if question.context.is_nsfw -%}")
(span
("title" "NSFW community")
("class" "flex items-center")
("style" "color: var(--color-primary)")
(text "{{ icon \"square-asterisk\" }}"))
(text "{%- endif %} {% if question.community > 0 and show_community -%}")
(a
("href" "/api/v1/communities/find/{{ question.community }}")
("class" "flex items-center")
(text "{{ self::community_avatar(id=question.community, size=\"24px\") }}"))
(text "{%- endif %} {% if question.is_global -%}")
(a
("class" "notification chip")
("href" "/question/{{ question.id }}")
(text "{{ question.answer_count }} answers"))
(text "{%- endif %}"))
(span
("class" "no_p_margin")
("style" "font-weight: 500")
("id" "question_content:{{ question.id }}")
(text "{{ question.content|markdown|safe }}"))
; question drawings
(text "{{ self::post_media(upload_ids=question.drawings) }}")
; anonymous user ip thing
; this is only shown if the post author is anonymous AND we are a helper
(text "{% if is_helper and owner.id == 0 %}")
(details
("class" "card tiny lowered w-full")
(summary
("class" "w-full flex gap-2 flex-wrap items-center")
(icon (text "shield"))
(span (text "View IP")))
(pre (code (text "{{ question.ip }}"))))
(text "{% endif %}")
; ...
(div
("class" "flex gap-2 items-center justify-between"))))
(text "{%- endmacro %} {% macro create_question_form(receiver=\"0\", community=\"\", header=\"\", is_global=false, drawing_enabled=false) -%}")
(div
("class" "card-nest")
(div
("class" "card small flex items-center gap-2")
(text "{{ icon \"message-circle-heart\" }}")
(span
("class" "no_p_margin")
(text "{% if header -%} {{ header|markdown|safe }} {% else %} {{ text \"requests:label.ask_question\" }} {%- endif %}")))
(form
("class" "card flex flex-col gap-2")
("onsubmit" "create_question_from_form(event)")
(div
("class" "flex flex-col gap-1")
; carp canvas
(text "{% if drawing_enabled -%}")
(div ("ui_ident" "carp_canvas_field"))
(text "{%- endif %}")
; form
(label
("for" "content")
(text "{{ text \"communities:label.content\" }}"))
(textarea
("type" "text")
("name" "content")
("id" "content")
("placeholder" "content")
("required" "")
("minlength" "2")
("maxlength" "4096")))
(div
("class" "flex gap-2")
(button
("class" "primary")
(text "{{ text \"communities:action.create\" }}"))
(text "{% if drawing_enabled -%}")
(button
("class" "lowered")
("ui_ident" "add_drawing")
("onclick" "attach_drawing()")
("type" "button")
(text "{{ text \"communities:action.draw\" }}"))
(button
("class" "lowered red hidden")
("ui_ident" "remove_drawing")
("onclick" "remove_drawing()")
("type" "button")
(text "{{ text \"communities:action.remove_drawing\" }}"))
(script
(text "globalThis.attach_drawing = async () => {
globalThis.gerald = await trigger(\"carp::new\", [document.querySelector(\"[ui_ident=carp_canvas_field]\")]);
globalThis.gerald.create_canvas();
document.querySelector(\"[ui_ident=add_drawing]\").classList.add(\"hidden\");
document.querySelector(\"[ui_ident=remove_drawing]\").classList.remove(\"hidden\");
}
globalThis.remove_drawing = async () => {
if (
!(await trigger(\"atto::confirm\", [
\"Are you sure you would like to do this?\",
]))
) {
return;
}
document.querySelector(\"[ui_ident=carp_canvas_field]\").innerHTML = \"\";
globalThis.gerald = null;
document.querySelector(\"[ui_ident=add_drawing]\").classList.remove(\"hidden\");
document.querySelector(\"[ui_ident=remove_drawing]\").classList.add(\"hidden\");
}"))
(text "{%- endif %}"))))
(script
(text "globalThis.gerald = null;
async function create_question_from_form(e) {
e.preventDefault();
await trigger(\"atto::debounce\", [\"questions::create\"]);
// create body
const body = new FormData();
if (globalThis.gerald) {
body.append(\"drawing.carpgraph\", new Blob([new Uint8Array(globalThis.gerald.as_carp2())], {
type: \"application/octet-stream\"
}));
}
body.append(
\"body\",
JSON.stringify({
content: e.target.content.value,
receiver: \"{{ receiver }}\",
community: \"{{ community }}\",
is_global: \"{{ is_global }}\" == \"true\",
}),
);
// ...
fetch(\"/api/v1/questions\", {
method: \"POST\",
body,
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
if (res.ok) {
e.target.reset();
if (globalThis.gerald) {
globalThis.gerald.clear();
}
}
});
}"))
(text "{%- endmacro %} {% macro global_question(question, can_manage_questions=false, secondary=false, show_community=true) -%}")
(div
("class" "card-nest")
(text "{{ 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 }}")
(text "{{ self::likes(id=question[0].id, asset_type=\"Question\", likes=question[0].likes, dislikes=question[0].dislikes, secondary=false) }}"))
(div
("class" "flex gap-1 buttons_box")
(a
("href" "/question/{{ question[0].id }}")
("class" "button small")
(text "{{ icon \"external-link\" }} {% if user -%}")
(span
(text "{{ text \"requests:label.answer\" }}"))
(text "{% else %}")
(span
(text "{{ text \"general:action.open\" }}"))
(text "{%- endif %}"))
(text "{% 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")
(text "{{ icon \"ellipsis\" }}"))
(div
("class" "inner")
(button
("class" "camo small red")
("onclick" "trigger('me::remove_question', ['{{ question[0].id }}'])")
(text "{{ icon \"trash\" }}")
(span
(text "{{ text \"general:action.delete\" }}")))))
(text "{%- endif %} {%- endif %}"))))
(text "{%- 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
(text "Listening on"))
(text "{{ icon \"spotify\" }}"))
(span
("class" "fade date short")
(text "{{ state.data.timestamp }}")))
(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 }}")))
(div
("class" "flex flex-col")
(h5
("class" "w-full")
(a
("href" "{{ state.external_urls.track }}")
("class" "flush")
(text "{{ state.data.track }}")))
(span
("class" "fade")
(a
("href" "{{ state.external_urls.artist }}")
("class" "flush")
(text "{{ state.data.artist }}")))
(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")))))
(text "{%- 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
(text "Listening on"))
(text "{{ icon \"last_fm\" }}"))
(span
("class" "fade date short")
(text "{{ state.data.timestamp }}")))
(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 }}")))
(div
("class" "flex flex-col")
(h5
("class" "w-full")
(a
("href" "{{ state.external_urls.track }}")
("class" "flush")
(text "{{ state.data.track }}")))
(span
("class" "fade")
(a
("href" "{{ state.external_urls.artist }}")
("class" "flush")
(text "{{ state.data.artist }}")))
(text "{% 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"))
(text "{%- endif %}"))))
(text "{%- endif %} {%- endmacro %} {% macro connection_icon(key) -%}")
(div
("style" "display: contents;")
(text "{% if key == \"Spotify\" -%} {{ icon \"spotify\" }} {% elif key == \"LastFm\" %} {{ icon \"last_fm\" }} {%- endif %}"))
(text "{%- 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, owner, message, owner) -%}")
(div
("class" "dropdown")
(button
("class" "camo small")
("onclick" "trigger('atto::hooks::dropdown', [event])")
("exclude" "dropdown")
(text "{{ icon \"ellipsis\" }}"))
(div
("class" "inner")
(text "{% if can_manage_message or (user and user.id == message.owner) -%}")
(button
("class" "red")
("onclick" "delete_message('{{ message.id }}')")
(text "{{ icon \"trash\" }}")
(span
(text "{{ text \"general:action.delete\" }}")))
(text "{%- endif %}")
(button
("onclick" "window.location.href = `${window.location.origin}/chats/{{ community }}/{{ channel }}?message={{ message.id }}`")
(text "{{ icon \"external-link\" }}")
(span
(text "{{ text \"general:action.open\" }}")))
(button
("onclick" "trigger('atto::copy_text', [`${window.location.origin}/chats/{{ community }}/{{ channel }}?message={{ message.id }}`])")
(text "{{ icon \"copy\" }}")
(span
(text "{{ text \"general:action.copy_link\" }}")))
(button
("onclick" "mention_user('{{ owner.username }}')")
(text "{{ icon \"at-sign\" }}")
(span
(text "{{ text \"chats:action.mention_user\" }}")))))
(text "{%- 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 }}")
(text "{% if not grouped -%}")
(a
("href" "/@{{ user.username }}")
("target" "_top")
(text "{{ self::avatar(username=user.username, size=\"42px\") }}"))
(text "{%- endif %}")
(div
("class" "flex flex-col gap-1 w-full")
(text "{% if not grouped -%}")
(div
("class" "flex gap-2 w-full justify-between flex-wrap")
(div
("class" "flex gap-2")
(text "{{ self::full_username(user=user) }} {% if message.edited != message.created %}")
(span
("class" "date")
(text "{{ message.edited }}")
(sup
("title" "Edited")
(text "*")))
(text "{% else %}")
(span
("class" "date")
(text "{{ message.created }}"))
(text "{%- endif %}"))
(div
("class" "flex gap-2 hidden")
(text "{{ self::message_actions(owner=user, message=message, can_manage_message=can_manage_message) }}")))
(text "{%- endif %}")
(div
("class" "flex w-full gap-2 justify-between")
(div
("class" "flex flex-col gap-2")
(span
("class" "no_p_margin")
(text "{{ message.content|markdown|safe }}"))
(div
("class" "flex w-full gap-1 flex-wrap")
("onclick" "window.EMOJI_PICKER_REACTION_MESSAGE_ID = '{{ message.id }}'")
("hook" "check_message_reactions")
("hook-arg:id" "{{ message.id }}")
(text "{% for emoji,num in message.reactions -%}")
(button
("class" "small lowered")
("ui_ident" "emoji_{{ emoji }}")
("onclick" "trigger('me::message_react', [event.target.parentElement.parentElement, '{{ message.id }}', '{{ emoji }}'])")
(span (text "{{ emoji|emojis|safe }} {{ num }}")))
(text "{%- endfor %}")
(div
("class" "hidden")
(text "{{ self::emoji_picker(element_id=\"react_emoji_picker_field\", render_dialog=false, render_button=true, small=true) }}"))))
(text "{% if grouped -%}")
(div
("class" "hidden")
(text "{{ self::message_actions(owner=user, message=message, can_manage_message=can_manage_message) }}"))
(text "{%- endif %}"))))
(text "{%- endmacro %} {% macro user_menu() -%}")
(div
("class" "inner")
(b
("class" "title")
(text "{{ user.username }}"))
(a
("href" "/@{{ user.username }}")
(text "{{ icon \"circle-user-round\" }}")
(span
(text "{{ text \"auth:link.my_profile\" }}")))
(a
("href" "/journals/0/0")
(icon (text "notebook"))
(str (text "general:link.journals")))
(text "{% if not user.settings.disable_achievements -%}")
(a
("href" "/achievements")
(icon (text "award"))
(str (text "general:link.achievements")))
(text "{%- endif %}")
(a
("href" "/settings")
(text "{{ icon \"settings\" }}")
(span
(text "{{ text \"auth:link.settings\" }}")))
(text "{% if is_helper -%}")
(b
("class" "title")
(text "{{ text \"general:label.mod\" }}"))
(a
("href" "/mod_panel/audit_log")
(text "{{ icon \"scroll-text\" }}")
(span
(text "{{ text \"general:link.audit_log\" }}")))
(a
("href" "/mod_panel/reports")
(text "{{ icon \"flag\" }}")
(span
(text "{{ text \"general:link.reports\" }}")))
(a
("href" "/mod_panel/ip_bans")
(text "{{ icon \"ban\" }}")
(span
(text "{{ text \"general:link.ip_bans\" }}")))
(a
("href" "/mod_panel/stats")
(text "{{ icon \"chart-line\" }}")
(span
(text "{{ text \"general:link.stats\" }}")))
(text "{%- endif %}")
(b ("class" "title") (text "{{ config.name }}"))
(a
("href" "https://trisua.com/t/tetratto")
("class" "button")
(icon (text "code"))
(str (text "general:link.source_code")))
(a
("href" "/reference/tetratto/index.html")
("class" "button")
("data-turbo" "false")
(icon (text "rabbit"))
(str (text "general:link.reference")))
(a
("href" "{{ config.policies.terms_of_service }}")
("class" "button")
(icon (text "heart-handshake"))
(text "Terms of service"))
(a
("href" "{{ config.policies.privacy }}")
("class" "button")
(icon (text "cookie"))
(text "Privacy policy"))
(b ("class" "title") (str (text "general:label.account")))
(button
("onclick" "trigger('me::switch_account')")
(text "{{ icon \"ellipsis\" }}")
(span
(text "{{ text \"general:action.switch_account\" }}")))
(button
("class" "red")
("onclick" "trigger('me::logout')")
(text "{{ icon \"log-out\" }}")
(span
(text "{{ text \"auth:action.logout\" }}"))))
(text "{%- endmacro %} {% macro user_status(other_user) -%} {% if other_user.settings.status %}")
(div
("class" "flex items-center gap-2")
(span
(text "{{ other_user.settings.status }}"))
; connection icon
(text "{% 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 %}"))
(text "{% 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")
(text "{{ icon \"music\" }}")
(span
(b
(text "Listening to "))
(text "{{ other_user.connections.LastFm[1].data.artist }}")))
(text "{% 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")
(text "{{ icon \"music\" }}")
(span
(b
(text "Listening to "))
(text "{{ other_user.connections.Spotify[1].data.artist }}")))
(text "{%- endif %} {%- endmacro %} {% macro user_plate(user, show_menu=false, show_kick=false, secondary=false) -%}")
(div
("class" "flex gap-2 items-center card tiny user_plate {% if secondary -%}secondary{%- endif %}")
(a
("href" "/@{{ user.username }}")
(text "{{ self::avatar(username=user.username, size=\"42px\", selector_type=\"username\") }}"))
(div
("class" "flex justify-center flex-col")
("style" "{% if show_menu or show_kick -%}width: 60%{% else %}max-width: 150px{%- endif %}")
(text "{{ self::full_username(user=user) }}")
(div
("class" "user_status")
(text "{{ self::user_status(other_user=user) }}")))
(text "{% if show_menu -%}")
(div
("class" "dropdown")
(button
("class" "camo small square")
("onclick" "trigger('atto::hooks::dropdown', [event])")
("exclude" "dropdown")
(text "{{ icon \"settings\" }}"))
(text "{{ self::user_menu() }}"))
(text "{% elif show_kick %}")
(div
("class" "dropdown")
("style" "margin-left: auto")
(button
("class" "camo small square")
("onclick" "trigger('atto::hooks::dropdown', [event])")
("exclude" "dropdown")
(text "{{ icon \"ellipsis\" }}"))
(div
("class" "inner")
(button
("class" "red")
("onclick" "kick_member('{{ channel.id }}', '{{ user.id }}')")
(text "{{ icon \"x\" }}")
(span
(text "{{ text \"chats:action.kick_member\" }}")))))
(text "{%- endif %}"))
(text "{%- endmacro %} {% macro emoji_picker(element_id, render_dialog=false, render_button=true, small=false) -%}")
(text "{% if render_button -%}")
(button
("class" "button small {% if not small -%} square {%- endif %} lowered")
("onclick" "window.EMOJI_PICKER_TEXT_ID = '{{ element_id }}'; document.getElementById('emoji_dialog').showModal()")
("title" "Emojis")
("type" "button")
(text "{{ icon \"smile-plus\" }}"))
(text "{%- endif %}")
(text "{% 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"))
(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: var(--pad-1);
box-shadow: 0 0 4px var(--color-shadow);
")
("class" "w-full"))
(script
(text "setTimeout(async () => {
document.querySelector(\"emoji-picker\").customEmoji =
await trigger(\"me::emojis\");
const style = document.createElement(\"style\");
style.textContent = `.custom-emoji { border-radius: 4px !important; } .category { font-weight: 600; }`;
document
.querySelector(\"emoji-picker\")
.shadowRoot.appendChild(style);
}, 150);
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;
}
if (event.detail.unicode) {
if (window.EMOJI_PICKER_MODE === \"replace\") {
document.getElementById(
window.EMOJI_PICKER_TEXT_ID,
).value = `:${await (
await fetch(\"/api/v1/lookup_emoji\", {
method: \"POST\",
body: event.detail.unicode,
})
).text()}:`;
} else {
document.getElementById(
window.EMOJI_PICKER_TEXT_ID,
).value += ` :${await (
await fetch(\"/api/v1/lookup_emoji\", {
method: \"POST\",
body: event.detail.unicode,
})
).text()}:`;
}
} else {
if (window.EMOJI_PICKER_MODE === \"replace\") {
document.getElementById(
window.EMOJI_PICKER_TEXT_ID,
).value = `:${event.detail.emoji.shortcodes[0]}:`;
} else {
document.getElementById(
window.EMOJI_PICKER_TEXT_ID,
).value += ` :${event.detail.emoji.shortcodes[0]}:`;
}
}
document.getElementById(
window.EMOJI_PICKER_TEXT_ID,
).dispatchEvent(new Event(\"change\"));
document.getElementById(\"emoji_dialog\").close();
});"))
(div
("class" "flex justify-between")
(div)
(div
("class" "flex gap-2")
(button
("class" "bold red lowered")
("onclick" "document.getElementById('emoji_dialog').close()")
("type" "button")
(text "{{ icon \"x\" }} {{ text \"dialog:action.close\" }}"))))))
(text "{%- endif %} {%- endmacro %} {% macro file_picker(files_list_id) -%}")
(button
("class" "button small square lowered")
("onclick" "pick_file()")
("title" "Images")
("type" "button")
(text "{{ icon \"image-up\" }}"))
(input
("type" "file")
("multiple" "")
("accept" "image/png,image/jpeg,image/avif,image/webp")
("style" "display: none")
("name" "file_picker"))
(div
("style" "display: none")
("id" "file_template")
(text "{{ icon \"image\" }}")
(b
("class" "name shorter")
("style" "overflow-wrap: normal")
(text ".file_name")))
(script
(text "(() => {
const input = document.querySelector(\"input[name=file_picker]\");
const element = document.getElementById(\"{{ files_list_id }}\");
const template = document.getElementById(\"file_template\");
globalThis.pick_file = () => {
input.click();
};
globalThis.render_file_picker_files = () => {
element.innerHTML = \"\";
let idx = 0;
for (const file of input.files) {
element.innerHTML += `<div class=\"card small secondary flex items-center gap-2\" onclick=\"remove_file(${idx})\" style=\"cursor: pointer\">${template.innerHTML.replace(
\".file_name\",
file.name,
)}</div>`;
idx += 1;
}
};
globalThis.remove_file = (idx) => {
const files = Array.from(input.files);
files.splice(idx - 1, 1);
// update files
const list = new DataTransfer();
for (item of files) {
list.items.add(item);
}
input.files = list.files;
// render
render_file_picker_files();
};
input.addEventListener(\"change\", () => {
render_file_picker_files();
});
})();"))
(text "{%- endmacro %} {% macro supporter_ad(body=\"\") -%} {% if config.stripe and not is_supporter %}")
(div
("class" "card w-full supporter_ad")
("ui_ident" "supporter_ad")
("onclick" "window.location.href = '/settings#/account/billing'")
(div
("class" "card w-full flex flex-wrap items-center gap-2 justify-between")
(text "{% if body -%}")
(b
(text "{{ body }}"))
(text "{% else %}")
(b
(text "{{ text \"general:label.supporter_motivation\" }}"))
(text "{%- endif %}")
(a
("href" "/settings#/account/billing")
("class" "button small")
(text "{{ icon \"heart\" }}")
(span
(text "{{ text \"general:action.become_supporter\" }}")))))
(text "{%- endif %} {%- endmacro %} {% macro create_post_options() -%}")
(div
("class" "flex gap-2 flex-wrap")
(text "{{ components::emoji_picker(element_id=\"content\", render_dialog=true) }} {% if not quoting -%} {% if is_supporter -%} {{ components::file_picker(files_list_id=\"files_list\") }} {%- endif %} {%- endif %}")
(button
("class" "small square lowered")
("title" "Add poll")
("onclick" "document.getElementById('poll_options_dialog').showModal()")
("type" "button")
(text "{{ icon \"list-todo\" }}"))
(button
("class" "small square lowered")
("title" "More options")
("onclick" "document.getElementById('post_options_dialog').showModal()")
("type" "button")
(text "{{ icon \"ellipsis\" }}"))
(label
("class" "flex items-center gap-1 button lowered")
("title" "Mark as NSFW/hide from public timelines")
("for" "is_nsfw")
(input
("type" "checkbox")
("name" "is_nsfw")
("id" "is_nsfw")
("checked" "{{ user.settings.auto_unlist }}")
("onchange" "POST_INITIAL_SETTINGS['is_nsfw'] = event.target.checked"))
(span (icon (text "eye-closed")))))
(dialog
("id" "post_options_dialog")
(div
("class" "inner flex flex-col gap-2")
(div
("id" "post_options")
("class" "flex flex-col gap-2"))
(hr)
(div
("class" "flex justify-between")
(div)
(div
("class" "flex gap-2")
(button
("class" "bold red lowered")
("onclick" "document.getElementById('post_options_dialog').close()")
("type" "button")
(text "{{ icon \"x\" }} {{ text \"dialog:action.close\" }}"))))
(script
(text "setTimeout(() => {
window.POST_INITIAL_SETTINGS = {
comments_enabled: true,
reposts_enabled: true,
reactions_enabled: true,
is_nsfw: false,
content_warning: \"\",
tags: [],
};
window.BLANK_INITIAL_SETTINGS = JSON.stringify(
window.POST_INITIAL_SETTINGS,
);
const settings_fields = [
[
[
\"comments_enabled\",
\"Allow people to comment on your post\",
],
window.POST_INITIAL_SETTINGS.comments_enabled.toString(),
\"checkbox\",
],
[
[
\"reposts_enabled\",
\"Allow people to repost/quote your post\",
],
window.POST_INITIAL_SETTINGS.reposts_enabled.toString(),
\"checkbox\",
],
[
[
\"reactions_enabled\",
\"Allow people to like/dislike your post\",
],
window.POST_INITIAL_SETTINGS.reactions_enabled.toString(),
\"checkbox\",
],
// [
// [\"is_nsfw\", \"Hide from public timelines\"],
// window.POST_INITIAL_SETTINGS.is_nsfw.toString(),
// \"checkbox\",
// ],
[
[\"content_warning\", \"Content warning\"],
window.POST_INITIAL_SETTINGS.content_warning,
\"textarea\",
],
[
[\"tags\", \"Tags\"],
window.POST_INITIAL_SETTINGS.tags,
\"input\",
{
embed_html:
'<span class=\"fade\">Tags should be separated by a comma.</span>',
},
],
];
document.getElementById(\"post_options\").innerHTML = \"\";
trigger(\"ui::generate_settings_ui\", [
document.getElementById(\"post_options\"),
settings_fields,
window.POST_INITIAL_SETTINGS,
{
tags: (new_tags) => {
window.POST_INITIAL_SETTINGS.tags = new_tags
.split(\",\")
.map((t) => t.trim());
},
},
]);
}, 250);
globalThis.update_settings_maybe = async (id) => {
if (
JSON.stringify(window.POST_INITIAL_SETTINGS) !==
window.BLANK_INITIAL_SETTINGS
) {
await fetch(`/api/v1/posts/${id}/context`, {
method: \"POST\",
headers: {
\"Content-Type\": \"application/json\",
},
body: JSON.stringify({
context: window.POST_INITIAL_SETTINGS,
}),
});
}
};"))))
; poll data manager function and dialog
;
; `get_poll_data` returns `[bool, string | PollData]`, where the string in arg 1
; represents an error message if arg 0 is `false`
(script
(text "window.POLL_OPTION_A = \"\";
window.POLL_OPTION_B = \"\";
window.POLL_OPTION_C = \"\";
window.POLL_OPTION_D = \"\";
window.POLL_EXPIRES = null;
window.get_poll_data = () => {
if (!POLL_OPTION_A && !POLL_OPTION_B) {
return [true, null];
}
if (POLL_OPTION_A && !POLL_OPTION_B || POLL_OPTION_B && !POLL_OPTION_A) {
return [false, \"At least 2 options are required for a poll\"];
}
if (POLL_EXPIRES < 0) {
return [false, \"Polls cannot time travel\"];
}
return [true, {
option_a: POLL_OPTION_A,
option_b: POLL_OPTION_B,
option_c: POLL_OPTION_C,
option_d: POLL_OPTION_D,
expires: POLL_EXPIRES,
}];
}"))
(dialog
("id" "poll_options_dialog")
(div
("class" "inner flex flex-col gap-2")
(div
("id" "poll_options")
("class" "flex flex-col gap-2")
(b (text "Attach poll"))
(div
("class" "card flex flex-col gap-2")
(span
(b (text "Option A "))
(span ("class" "fade red") (text "(required)")))
(input ("type" "text") ("placeholder" "option A") ("onchange" "window.POLL_OPTION_A = event.target.value")))
(div
("class" "card flex flex-col gap-2")
(span
(b (text "Option B "))
(span ("class" "fade red") (text "(required)")))
(input ("type" "text") ("placeholder" "option B") ("onchange" "window.POLL_OPTION_B = event.target.value")))
(div
("class" "card flex flex-col gap-2")
(b (text "Option C"))
(input ("type" "text") ("placeholder" "option A") ("onchange" "window.POLL_OPTION_C = event.target.value")))
(div
("class" "card flex flex-col gap-2")
(b (text "Option D"))
(input ("type" "text") ("placeholder" "option D") ("onchange" "window.POLL_OPTION_D = event.target.value")))
(div
("class" "card flex flex-col gap-2")
(b (text "Expires"))
(input ("type" "date") ("onchange" "window.POLL_EXPIRES = event.target.valueAsDate.getTime() - new Date().getTime()"))))
(hr)
(div
("class" "flex justify-between")
(div)
(div
("class" "flex gap-2")
(button
("class" "bold red lowered")
("onclick" "document.getElementById('poll_options_dialog').close()")
("type" "button")
(text "{{ icon \"x\" }} {{ text \"dialog:action.close\" }}"))))))
(text "{%- endmacro %}")
(text "{% macro poll(post, poll) -%}")
(div
("class" "card lowered w-full flex flex-col gap-2")
(text "{% set total = poll[0].votes_a + poll[0].votes_b + poll[0].votes_c + poll[0].votes_d %}")
(text "{% if poll[1] or poll[2] or user and user.id == poll[0].owner -%}")
; already voted, show results
(text "{% if poll[1] %}")
(span ("class" "fade") (text "You've already voted!"))
(text "{% elif poll[2] %}")
(span ("class" "fade") (text "Poll ended!"))
(text "{% endif %}")
; option a
(div
("class" "card w-full flex flex-col gap-2")
(span (text "{{ poll[0].option_a }} ({{ poll[0].votes_a }} votes)"))
(div ("class" "poll_bar") ("style" "width: {{ (poll[0].votes_a / total) * 100 }}%")))
; option b
(div
("class" "card w-full flex flex-col gap-2")
(span (text "{{ poll[0].option_b }} ({{ poll[0].votes_b }} votes)"))
(div ("class" "poll_bar") ("style" "width: {{ (poll[0].votes_b / total) * 100 }}%")))
; option c
(text "{% if poll[0].option_c -%}")
(div
("class" "card w-full flex flex-col gap-2")
(span (text "{{ poll[0].option_c }} ({{ poll[0].votes_c }} votes)"))
(div ("class" "poll_bar") ("style" "width: {{ (poll[0].votes_c / total) * 100 }}%")))
(text "{%- endif %}")
; option d
(text "{% if poll[0].option_d -%}")
(div
("class" "card w-full flex flex-col gap-2")
(span (text "{{ poll[0].option_d }} ({{ poll[0].votes_d }} votes)"))
(div ("class" "poll_bar") ("style" "width: {{ (poll[0].votes_d / total) * 100 }}%")))
(text "{%- endif %}")
(text "{% else %}")
; not voted yet, just show options so user can vote
; option a
(button
("class" "hover_left_bar raised justify-start w-full poll_option")
("onclick" "trigger('me::vote', ['{{ post.id }}', 'A'])")
(icon (text "tally-1"))
(text "{{ poll[0].option_a }}"))
; option b
(button
("class" "hover_left_bar raised justify-start w-full poll_option")
("onclick" "trigger('me::vote', ['{{ post.id }}', 'B'])")
(icon (text "tally-2"))
(text "{{ poll[0].option_b }}"))
; option c
(text "{% if poll[0].option_c -%}")
(button
("class" "hover_left_bar raised justify-start w-full poll_option")
("onclick" "trigger('me::vote', ['{{ post.id }}', 'C'])")
(icon (text "tally-3"))
(text "{{ poll[0].option_c }}"))
(text "{%- endif %}")
; option d
(text "{% if poll[0].option_d -%}")
(button
("class" "hover_left_bar raised justify-start w-full poll_option")
("onclick" "trigger('me::vote', ['{{ post.id }}', 'D'])")
(icon (text "tally-4"))
(text "{{ poll[0].option_d }}"))
(text "{%- endif %}")
(text "{%- endif %}")
; show expiration date + totals
(div
("class" "flex w-full flex-wrap gap-2")
(span ("class" "notification chip") (text "{{ total }} votes"))
(text "{% if not poll[2] -%}")
(span
("class" "notification chip")
(text "Expires in ")
(span
("class" "poll_date")
("data-created" "{{ poll[0].created }}")
("data-expires" "{{ poll[0].expires }}")))
(text "{%- endif %}")))
(text "{%- endmacro %}")
(text "{% macro community_info(community) %}")
(div
("class" "card-nest flex flex-col")
(div
("id" "bio")
("class" "card small no_p_margin")
(text "{{ community.context.description|markdown|safe }}"))
(div
("class" "card flex flex-col gap-2")
(div
("class" "w-full flex justify-between items-center")
(span
("class" "notification chip")
(text "ID"))
(button
("title" "Copy")
("onclick" "trigger('atto::copy_text', ['{{ community.id }}'])")
("class" "camo small")
(text "{{ icon \"copy\" }}")))
(div
("class" "w-full flex justify-between items-center")
(span
("class" "notification chip")
(text "Created "))
(span
("class" "date")
(text "{{ community.created }}")))
(div
("class" "w-full flex justify-between items-center")
(span
("class" "notification chip")
(text "Members"))
(a
("href" "/community/{{ community.title }}/members")
(text "{{ community.member_count }}")))
(div
("class" "w-full flex justify-between items-center")
(span
("class" "notification chip")
(text "Posts"))
(a
("href" "/community/{{ community.title }}")
(text "{{ community.post_count }}")))
(div
("class" "w-full flex justify-between items-center")
(span
("class" "notification chip")
(text "Score"))
(div
("class" "flex gap-2")
(b
(text "{{ community.likes - community.dislikes }}"))
(text "{% if user -%}")
(div
("class" "flex gap-1 reactions_box")
("hook" "check_reactions")
("hook-arg:id" "{{ community.id }}")
(text "{{ components::likes(id=community.id, asset_type=\"Community\", likes=community.likes, dislikes=community.dislikes) }}"))
(text "{%- endif %}")))))
(text "{% endmacro %}")
(text "{% macro community_actions(community) -%}")
(text "{% if user -%}")
(div
("class" "card flex gap-2 flex-wrap")
("id" "join_or_leave")
(text "{% if not is_owner -%} {% if not is_joined -%} {% if not is_pending %}")
(button
("class" "primary")
("onclick" "join_community()")
(text "{{ icon \"circle-plus\" }}")
(span
(text "{{ text \"communities:action.join\" }}")))
(script
(text "globalThis.join_community = () => {
fetch(
\"/api/v1/communities/{{ community.id }}/join\",
{
method: \"POST\",
},
)
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
setTimeout(() => {
window.location.reload();
}, 150);
});
};"))
(text "{% else %}")
(button
("class" "lowered red")
("onclick" "cancel_request()")
(text "{{ icon \"x\" }}")
(span
(text "{{ text \"communities:action.cancel_request\" }}")))
(script
(text "globalThis.cancel_request = async () => {
if (
!(await trigger(\"atto::confirm\", [
\"Are you sure you would like to do this?\",
]))
) {
return;
}
fetch(
\"/api/v1/communities/{{ community.id }}/memberships/{{ user.id }}\",
{
method: \"DELETE\",
},
)
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
setTimeout(() => {
window.location.reload();
}, 150);
});
};"))
(text "{%- endif %} {% else %}")
(button
("class" "lowered red")
("onclick" "leave_community()")
(text "{{ icon \"circle-minus\" }}")
(span
(text "{{ text \"communities:action.leave\" }}")))
(a
("href" "/chats/{{ community.id }}/0")
("class" "button lowered")
(text "{{ icon \"message-circle\" }}")
(span
(text "{{ text \"communities:label.chats\" }}")))
(text "{% if user and can_post -%}")
(a
("href" "/communities/intents/post?community={{ community.id }}")
("class" "button lowered")
("data-turbo" "false")
(text "{{ icon \"plus\" }}")
(span
(text "{{ text \"general:action.post\" }}")))
(text "{%- endif %}")
(script
(text "globalThis.leave_community = async () => {
if (
!(await trigger(\"atto::confirm\", [
\"Are you sure you would like to do this?\",
]))
) {
return;
}
fetch(
\"/api/v1/communities/{{ community.id }}/memberships/{{ user.id }}\",
{
method: \"DELETE\",
},
)
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
setTimeout(() => {
window.location.reload();
}, 150);
});
};"))
(text "{%- endif %} {% else %}")
(a
("href" "/chats/{{ community.id }}/0")
("class" "button lowered")
(text "{{ icon \"message-circle\" }}")
(span
(text "{{ text \"communities:label.chats\" }}")))
(a
("href" "/communities/intents/post?community={{ community.id }}")
("class" "button lowered")
("data-turbo" "false")
(text "{{ icon \"plus\" }}")
(span
(text "{{ text \"general:action.post\" }}")))
(text "{%- endif %} {% if can_manage_community or is_manager -%}")
(a
("href" "/community/{{ community.id }}/manage")
("class" "button primary")
(text "{{ icon \"settings\" }}")
(span
(text "{{ text \"communities:action.configure\" }}")))
(text "{%- endif %}"))
(text "{%- endif %}")
(text "{%- endmacro %}")
(text "{% macro ticket(post, owner) -%}")
(div
("href" "/post/{{ post.id }}")
("class" "card secondary w-fill flex flex-col gap-2")
(div
("class" "flex gap-2 items-center")
; user info
(a
("href" "/@{{ owner.username }}")
(text "{{ self::avatar(username=owner.username, size=\"24px\", selector_type=\"username\") }}"))
(span
("class" "name")
(text "{{ self::full_username(user=owner) }}"))
; timestamp
(span ("class" "date") (text "{{ post.created }}"))
; pinned
(text "{% if post.context.is_pinned -%}")
(icon (text "pin"))
(text "{%- endif %}"))
; post title
(a
("href" "/post/{{ post.id }}")
("class" "flush flex gap-2 items-center")
; open/closed icon
(text "{% if community and community.is_forge -%} {% if post.is_open -%}")
(span
("title" "Open")
("class" "flex items-center green")
(text "{{ icon \"circle-dot\" }}"))
(text "{% else %}")
(span
("title" "Closed")
("class" "flex items-center purple")
(text "{{ icon \"circle-check\" }}"))
(text "{%- endif %} {%- endif %}")
(h4
("class" "no_p_margin")
(text "{{ post.title|markdown|safe }}"))))
(text "{%- endmacro %}")
(text "{% macro stack_listing(stack) -%}")
(a
("href" "/stacks/{{ stack.id }}")
("class" "card secondary flex flex-col gap-2")
(div
("class" "flex items-center gap-2")
(text "{{ icon \"list\" }}")
(b
(text "{{ stack.name }}")))
(span
(text "Created ")
(span
("class" "date")
(text "{{ stack.created }}"))
(text "; {{ stack.privacy }}; {{ stack.users|length }} users")))
(text "{%- endmacro %}")
(text "{% macro journal_listing(journal, notes, selected_note, selected_journal, view_mode=false, owner=false) -%}")
(text "{% if selected_journal != journal.id -%}")
; not selected
(div
("class" "flex flex-row gap-1")
(a
("href" "/journals/{{ journal.id }}/0?view={{ view_mode }}")
("class" "button justify-start lowered w-full")
(icon (text "notebook"))
(text "{{ journal.title }}"))
(div
("class" "dropdown")
(button
("class" "big_icon lowered")
("onclick" "trigger('atto::hooks::dropdown', [event])")
("exclude" "dropdown")
("style" "width: 32px")
(text "{{ icon \"ellipsis\" }}"))
(div
("class" "inner")
(button
("onclick" "delete_journal('{{ journal.id }}')")
("class" "red")
(text "{{ icon \"trash\" }}")
(span
(text "{{ text \"general:action.delete\" }}"))))))
(text "{% else %}")
; selected
(div
("class" "flex flex-row gap-1")
(button
("class" "justify-start lowered w-full")
(icon (text "arrow-down"))
(text "{{ journal.title }}"))
(text "{% if user and user.id == journal.owner -%}")
(div
("class" "dropdown")
(button
("class" "big_icon lowered")
("onclick" "trigger('atto::hooks::dropdown', [event])")
("exclude" "dropdown")
("style" "width: 32px")
(text "{{ icon \"ellipsis\" }}"))
(div
("class" "inner")
(a
("class" "button")
("href" "/journals/{{ journal.id }}/0?view={{ view_mode }}")
(icon (text "house"))
(str (text "general:link.home")))
(button
("onclick" "delete_journal('{{ journal.id }}')")
("class" "red")
(icon (text "trash"))
(str (text "general:action.delete")))))
(text "{%- endif %}"))
(text "{% if selected_note -%}")
; open all details elements above the selected note
(script
("defer" "true")
(text "setTimeout(() => {
let cursor = document.querySelector(\"[ui_ident=active_note]\");
while (cursor) {
if (cursor.nodeName === \"DETAILS\") {
cursor.setAttribute(\"open\", \"true\");
}
cursor = cursor.parentElement;
}
}, 150);"))
(text "{%- endif %}")
(div
("class" "flex flex-col gap-2")
("style" "margin-left: 10px; padding-left: 5px; border-left: solid 2px var(--color-super-lowered); width: calc(100% - 10px)")
; create note
(text "{% if user and user.id == journal.owner -%}")
(button
("class" "lowered justify-start w-full")
("onclick" "create_note()")
(icon (text "plus"))
(str (text "journals:action.create_note")))
(text "{%- endif %}")
; note listings
(text "{{ self::notes_list_dir_listing_inner(dir=[0, 0, \"root\"], dirs=journal.dirs, notes=notes, owner=owner, journal=journal, view_mode=view_mode) }}"))
(text "{%- endif %}")
(text "{%- endmacro %}")
(text "{% macro notes_list_dir_listing(dir, dirs, notes, owner, journal, view_mode=false) -%}")
(details
(summary
("class" "button w-full justify-start raised w-full")
(icon (text "folder"))
(text "{{ dir[2] }}"))
(div
("class" "flex flex-col gap-2")
("style" "margin-left: 10px; padding-left: 5px; border-left: solid 2px var(--color-super-lowered); width: calc(100% - 10px)")
(text "{{ self::notes_list_dir_listing_inner(dir=dir, dirs=dirs, notes=notes, owner=owner, journal=journal, view_mode=view_mode) }}")))
(text "{%- endmacro %}")
(text "{% macro notes_list_dir_listing_inner(dir, dirs, notes, owner, journal, view_mode=false) -%}")
; child dirs
(text "{% for subdir in dirs %} {% if subdir[1] == dir[0] -%}")
(text "{{ self::notes_list_dir_listing(dir=subdir, dirs=dirs, notes=notes, owner=owner, journal=journal) }}")
(text "{%- endif %} {% endfor %}")
; child notes
(text "{% for note in notes %} {% if note.dir == dir[0] -%} {% if not view_mode or note.title != \"journal.css\" -%}")
(text "{{ self::notes_list_note_listing(note=note, owner=owner, journal=journal) }}")
(text "{%- endif %} {%- endif %} {% endfor %}")
(text "{%- endmacro %}")
(text "{% macro notes_list_note_listing(owner, journal, note) -%}")
(div
("class" "flex flex-row gap-1")
("ui_ident" "{% if selected_note == note.id -%} active_note {%- else -%} inactive_note {%- endif %}")
(a
("href" "{% if owner -%} /@{{ owner.username }}/{{ journal.title }}/{{ note.title }} {%- else -%} /journals/{{ journal.id }}/{{ note.id }} {%- endif %}")
("class" "button justify-start w-full {% if selected_note == note.id -%} lowered {%- else -%} raised {%- endif %}")
(icon (text "file-text"))
(text "{{ note.title }}"))
(text "{% if user and user.id == journal.owner -%}")
(div
("class" "dropdown")
(button
("class" "big_icon {% if selected_note == note.id -%} lowered {%- else -%} raised {%- endif %}")
("onclick" "trigger('atto::hooks::dropdown', [event])")
("exclude" "dropdown")
("style" "width: 32px")
(text "{{ icon \"ellipsis\" }}"))
(div
("class" "inner")
(button
("onclick" "change_note_title('{{ note.id }}')")
(icon (text "pencil"))
(str (text "chats:action.rename")))
(a
("href" "/journals/{{ journal.id }}/{{ note.id }}#/tags")
(icon (text "tag"))
(str (text "journals:action.edit_tags")))
(button
("onclick" "window.NOTE_MOVER_NOTE_ID = '{{ note.id }}'; document.getElementById('note_mover_dialog').showModal()")
(icon (text "brush-cleaning"))
(str (text "journals:action.move")))
(text "{% if note.is_global -%}")
(a
("class" "button")
("href" "/x/{{ note.title }}")
(icon (text "eye"))
(str (text "journals:action.view")))
(button
("class" "purple")
("onclick" "unpublish_note('{{ note.id }}')")
(icon (text "globe-lock"))
(str (text "journals:action.unpublish")))
(text "{% elif note.title != 'journal.css' %}")
(button
("class" "green")
("onclick" "publish_note('{{ note.id }}')")
(icon (text "globe"))
(str (text "journals:action.publish")))
(text "{%- endif %}")
(button
("onclick" "delete_note('{{ note.id }}')")
("class" "red")
(icon (text "trash"))
(str (text "general:action.delete")))))
(text "{%- endif %}"))
(text "{%- endmacro %}")
(text "{% macro note_tags(note) -%} {% if note and note.tags|length > 0 -%}")
(div
("class" "flex gap-1 flex-wrap")
(text "{% for tag in note.tags %}")
(a
("href" "{% if view_mode -%} /@{{ owner.username }} {%- else -%} /@{{ user.username }} {%- endif -%} /{{ journal.title }}?tag={{ tag }}")
("class" "notification chip")
(span (text "{{ tag }}")))
(text "{% endfor %}"))
(text "{%- endif %} {%- endmacro %}")
(text "{% macro directories_editor(dirs) -%}")
(button
("onclick" "create_directory('0')")
(icon (text "plus"))
(str (text "journals:action.create_root_dir")))
(text "{% for dir in dirs %} {% if dir[1] == 0 -%}")
(text "{{ self::directories_editor_listing(dir=dir, dirs=dirs) }}")
(text "{%- endif %} {% endfor %}")
(text "{%- endmacro %}")
(text "{% macro directories_editor_listing(dir, dirs) -%}")
(div
("class" "flex flex-row gap-1")
(button
("class" "justify-start lowered w-full")
(icon (text "folder-open"))
(text "{{ dir[2] }}"))
(div
("class" "dropdown")
(button
("class" "big_icon lowered")
("onclick" "trigger('atto::hooks::dropdown', [event])")
("exclude" "dropdown")
("style" "width: 32px")
(text "{{ icon \"ellipsis\" }}"))
(div
("class" "inner")
(button
("onclick" "create_directory('{{ dir[0] }}')")
(icon (text "plus"))
(str (text "journals:action.create_subdir")))
(button
("onclick" "delete_directory('{{ dir[0] }}')")
("class" "red")
(icon (text "trash"))
(str (text "general:action.delete"))))))
(div
("class" "flex flex-col gap-2")
("style" "margin-left: 10px; padding-left: 5px; border-left: solid 2px var(--color-super-lowered); width: calc(100% - 10px)")
; subdir listings
(text "{% for subdir in dirs %} {% if subdir[1] == dir[0] -%}")
(text "{{ self::directories_editor_listing(dir=subdir, dirs=dirs) }}")
(text "{%- endif %} {% endfor %}"))
(text "{%- endmacro %}")
(text "{% macro note_mover_dirs(dirs) -%}")
(text "{% for dir in dirs %} {% if dir[1] == 0 -%}")
(text "{{ self::note_mover_dirs_listing(dir=dir, dirs=dirs) }}")
(text "{%- endif %} {% endfor %}")
(text "{%- endmacro %}")
(text "{% macro note_mover_dirs_listing(dir, dirs) -%}")
(button
("onclick" "move_note_dir(window.NOTE_MOVER_NOTE_ID, '{{ dir[0] }}'); document.getElementById('note_mover_dialog').close()")
("class" "justify-start lowered w-full")
(icon (text "folder-open"))
(text "{{ dir[2] }}"))
(div
("class" "flex flex-col gap-2")
("style" "margin-left: 10px; padding-left: 5px; border-left: solid 2px var(--color-super-lowered); width: calc(100% - 10px)")
; subdir listings
(text "{% for subdir in dirs %} {% if subdir[1] == dir[0] -%}")
(text "{{ self::note_mover_dirs_listing(dir=subdir, dirs=dirs) }}")
(text "{%- endif %} {% endfor %}"))
(text "{%- endmacro %}")