Monorepo: consolidate 7 repos into one
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m5s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m5s
Combines shared, blog, market, cart, events, federation, and account into a single repository. Eliminates submodule sync, sibling model copying at build time, and per-app CI orchestration. Changes: - Remove per-app .git, .gitmodules, .gitea, submodule shared/ dirs - Remove stale sibling model copies from each app - Update all 6 Dockerfiles for monorepo build context (root = .) - Add build directives to docker-compose.yml - Add single .gitea/workflows/ci.yml with change detection - Add .dockerignore for monorepo build context - Create __init__.py for federation and account (cross-app imports)
This commit is contained in:
21
shared/browser/templates/macros/admin_nav.html
Normal file
21
shared/browser/templates/macros/admin_nav.html
Normal file
@@ -0,0 +1,21 @@
|
||||
{#
|
||||
Shared admin navigation macro
|
||||
Use this instead of duplicate _nav.html files
|
||||
#}
|
||||
|
||||
{% macro admin_nav_item(href, icon='cog', label='', select_colours='', aclass=styles.nav_button) %}
|
||||
{% import 'macros/links.html' as links %}
|
||||
{% call links.link(href, hx_select_search, select_colours, True, aclass=aclass) %}
|
||||
<i class="fa fa-{{ icon }}" aria-hidden="true"></i>
|
||||
{{ label }}
|
||||
{% endcall %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro placeholder_nav() %}
|
||||
{# Placeholder for admin sections without specific nav items #}
|
||||
<div class="relative nav-group">
|
||||
<span class="block px-3 py-2 text-stone-400 text-sm italic">
|
||||
Admin options
|
||||
</span>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
31
shared/browser/templates/macros/cart_icon.html
Normal file
31
shared/browser/templates/macros/cart_icon.html
Normal file
@@ -0,0 +1,31 @@
|
||||
{# Cart icon/badge — shows logo when empty, cart icon with count when items present #}
|
||||
|
||||
{% macro cart_icon(count=0, oob=False) %}
|
||||
<div id="cart-mini" {% if oob %}hx-swap-oob="{{oob}}"{% endif %}>
|
||||
{% if count == 0 %}
|
||||
<div class="h-12 w-12 rounded-full overflow-hidden border border-stone-300 flex-shrink-0">
|
||||
<a
|
||||
href="{{ blog_url('/') }}"
|
||||
class="h-full w-full font-bold text-5xl flex-shrink-0 flex flex-row items-center gap-1"
|
||||
>
|
||||
<img
|
||||
src="{{ site().logo }}"
|
||||
class="h-full w-full rounded-full object-cover border border-stone-300 flex-shrink-0"
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<a
|
||||
href="{{ cart_url('/') }}"
|
||||
class="relative inline-flex items-center justify-center text-stone-700 hover:text-emerald-700"
|
||||
>
|
||||
<i class="fa fa-shopping-cart text-5xl" aria-hidden="true"></i>
|
||||
<span
|
||||
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 inline-flex items-center justify-center rounded-full bg-emerald-600 text-white text-sm w-5 h-5"
|
||||
>
|
||||
{{ count }}
|
||||
</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
17
shared/browser/templates/macros/glyphs.html
Normal file
17
shared/browser/templates/macros/glyphs.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{% macro opener(group=False) %}
|
||||
<svg
|
||||
class="h-4 w-4 transition-transform group-open{{ '/' + group if group else ''}}:rotate-180"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 9l6 6 6-6"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
{% endmacro %}
|
||||
61
shared/browser/templates/macros/layout.html
Normal file
61
shared/browser/templates/macros/layout.html
Normal file
@@ -0,0 +1,61 @@
|
||||
{# templates/macros/layout.html #}
|
||||
|
||||
{% macro details(group = '', _class='') %}
|
||||
<details
|
||||
class="group{{group}} p-2 {{_class}}" data-toggle-group="mobile-panels">
|
||||
{{ caller() }}
|
||||
</details>
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro summary(id, _class=None, oob=False) %}
|
||||
<summary>
|
||||
<header class="z-50">
|
||||
<div
|
||||
id="{{id}}"
|
||||
{% if oob %}
|
||||
hx-swap-oob="true"
|
||||
{% endif %}
|
||||
class="{{'flex justify-between items-start gap-2' if not _class else _class}}">
|
||||
{{ caller() }}
|
||||
</div>
|
||||
</header>
|
||||
</summary>
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro filter_summary(id, current_local_href, search, search_count, hx_select, oob=True) %}
|
||||
<summary class="bg-white/90">
|
||||
<div class="flex flex-row items-start">
|
||||
<div>
|
||||
<div class="md:hidden mx-2 bg-stone-200 rounded">
|
||||
<span class="flex items-center justify-center text-stone-600 text-lg h-12 w-12 transition-transform group-open/filter:hidden self-start">
|
||||
<i class="fa-solid fa-filter"></i>
|
||||
</span>
|
||||
<span>
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24"
|
||||
class="w-12 h-12 rotate-180 transition-transform group-open/filter:block hidden self-start">
|
||||
<path d="M6 9l6 6 6-6" fill="currentColor"/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="{{id}}"
|
||||
class="flex-1 md:hidden grid grid-cols-12 items-center gap-3"
|
||||
>
|
||||
<div class="flex flex-col items-start gap-2">
|
||||
{{ caller() }}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% from 'macros/search.html' import search_mobile %}
|
||||
{{ search_mobile(current_local_href, search, search_count, hx_select) }}
|
||||
</div>
|
||||
</summary>
|
||||
{%- endmacro %}
|
||||
|
||||
|
||||
{% macro menu(id, _class="") %}
|
||||
<div id="{{id}}" hx-swap-oob="outerHTML" class="{{_class}}">
|
||||
{{ caller() }}
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
59
shared/browser/templates/macros/links.html
Normal file
59
shared/browser/templates/macros/links.html
Normal file
@@ -0,0 +1,59 @@
|
||||
|
||||
|
||||
{% macro link(url, select, select_colours='', highlight=True, _class='', aclass='') %}
|
||||
{% set href=url|host%}
|
||||
<div class="relative nav-group {{_class}}">
|
||||
<a
|
||||
href="{{ href }}"
|
||||
hx-get="{{ href }}"
|
||||
hx-target="#main-panel"
|
||||
hx-select="{{select}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
aria-selected="{{ 'true' if (request.path|host).startswith(href) else 'false' }}"
|
||||
{% if aclass %}
|
||||
class="{{aclass}}"
|
||||
{% elif select_colours %}
|
||||
class="whitespace-normal flex gap-2 px-3 py-2 rounded
|
||||
text-center break-words leading-snug
|
||||
bg-stone-200 text-black
|
||||
{{select_colours if highlight else ''}}
|
||||
"
|
||||
{% else %}
|
||||
class="w-full whitespace-normal flex items-center gap-2 font-bold text-2xl px-3 py-2"
|
||||
{% endif %}
|
||||
>
|
||||
{{ caller() }}
|
||||
</a>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro menu_row(id=False, oob=False) %}
|
||||
<div
|
||||
{% if id %}
|
||||
id="{{id}}"
|
||||
{% endif %}
|
||||
{% if oob %}
|
||||
hx-swap-oob="outerHTML"
|
||||
{% endif %}
|
||||
class="flex flex-col items-center md:flex-row justify-center md:justify-between w-full p-1 bg-{{menu_colour}}-{{(500-(level()*100))|string}}"
|
||||
>
|
||||
{{ caller() }}
|
||||
</div>
|
||||
{{level_up()}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro desktop_nav() %}
|
||||
<nav class="hidden md:flex gap-4 text-sm ml-2 justify-end items-center flex-0">
|
||||
{{ caller() }}
|
||||
</nav>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro admin() %}
|
||||
<i class="fa fa-cog" aria-hidden="true"></i>
|
||||
<div>
|
||||
settings
|
||||
</div>
|
||||
|
||||
{% endmacro %}
|
||||
68
shared/browser/templates/macros/scrolling_menu.html
Normal file
68
shared/browser/templates/macros/scrolling_menu.html
Normal file
@@ -0,0 +1,68 @@
|
||||
{#
|
||||
Scrolling menu macro with arrow navigation
|
||||
|
||||
Creates a horizontally scrollable menu (desktop) or vertically scrollable (mobile)
|
||||
with arrow buttons that appear/hide based on content overflow.
|
||||
|
||||
Parameters:
|
||||
- container_id: Unique ID for the scroll container
|
||||
- items: List of items to iterate over
|
||||
- item_content: Caller block that renders each item (receives 'item' variable)
|
||||
- wrapper_class: Optional additional classes for outer wrapper
|
||||
- container_class: Optional additional classes for scroll container
|
||||
- item_class: Optional additional classes for each item wrapper
|
||||
#}
|
||||
|
||||
{% macro scrolling_menu(container_id, items, wrapper_class='', container_class='', item_class='') %}
|
||||
{% if items %}
|
||||
{# Left scroll arrow - desktop only #}
|
||||
<button
|
||||
class="scrolling-menu-arrow-{{ container_id }} hidden flex-shrink-0 p-2 hover:bg-stone-200 rounded"
|
||||
aria-label="Scroll left"
|
||||
_="on click
|
||||
set #{{ container_id }}.scrollLeft to #{{ container_id }}.scrollLeft - 200">
|
||||
<i class="fa fa-chevron-left"></i>
|
||||
</button>
|
||||
|
||||
{# Scrollable container #}
|
||||
<div id="{{ container_id }}"
|
||||
class="overflow-y-auto sm:overflow-x-auto sm:overflow-y-visible scrollbar-hide max-h-[50vh] sm:max-h-none {{ container_class }}"
|
||||
style="scroll-behavior: smooth;"
|
||||
_="on load or scroll
|
||||
-- Show arrows if content overflows (desktop only)
|
||||
if window.innerWidth >= 640 and my.scrollWidth > my.clientWidth
|
||||
remove .hidden from .scrolling-menu-arrow-{{ container_id }}
|
||||
add .flex to .scrolling-menu-arrow-{{ container_id }}
|
||||
else
|
||||
add .hidden to .scrolling-menu-arrow-{{ container_id }}
|
||||
remove .flex from .scrolling-menu-arrow-{{ container_id }}
|
||||
end">
|
||||
<div class="flex flex-col sm:flex-row gap-1 {{ wrapper_class }}">
|
||||
{% for item in items %}
|
||||
<div class="{{ item_class }}">
|
||||
{{ caller(item) }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.scrollbar-hide {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
{# Right scroll arrow - desktop only #}
|
||||
<button
|
||||
class="scrolling-menu-arrow-{{ container_id }} hidden flex-shrink-0 p-2 hover:bg-stone-200 rounded"
|
||||
aria-label="Scroll right"
|
||||
_="on click
|
||||
set #{{ container_id }}.scrollLeft to #{{ container_id }}.scrollLeft + 200">
|
||||
<i class="fa fa-chevron-right"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
83
shared/browser/templates/macros/search.html
Normal file
83
shared/browser/templates/macros/search.html
Normal file
@@ -0,0 +1,83 @@
|
||||
{# Shared search input macros for filter UIs #}
|
||||
|
||||
{% macro search_mobile(current_local_href, search, search_count, hx_select) -%}
|
||||
<div
|
||||
id="search-mobile-wrapper"
|
||||
class="flex flex-row gap-2 items-center flex-1 min-w-0 pr-2"
|
||||
>
|
||||
<input
|
||||
id="search-mobile"
|
||||
type="text"
|
||||
name="search"
|
||||
aria-label="search"
|
||||
class="text-base md:text-sm col-span-5 rounded-md px-3 py-2 mb-2 w-full min-w-0 max-w-full border-2 border-stone-200 placeholder-shown:border-stone-200 [&:not(:placeholder-shown)]:border-yellow-200"
|
||||
hx-preserve
|
||||
value="{{ search|default('', true) }}"
|
||||
placeholder="search"
|
||||
hx-trigger="input changed delay:300ms"
|
||||
hx-target="#main-panel"
|
||||
|
||||
hx-select="{{hx_select}}, #search-mobile-wrapper, #search-desktop-wrapper"
|
||||
hx-get="{{ (current_local_href ~ {'search': None}|qs)|host }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
hx-headers='{"X-Origin":"search-mobile", "X-Search":"true"}'
|
||||
hx-sync="this:replace"
|
||||
autocomplete="off"
|
||||
>
|
||||
|
||||
<div
|
||||
id="search-count-mobile"
|
||||
aria-label="search count"
|
||||
{% if not search_count %}
|
||||
class="text-xl text-red-500"
|
||||
{% endif %}
|
||||
>
|
||||
{% if search %}
|
||||
{{search_count}}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro search_desktop(current_local_href, search, search_count, hx_select) -%}
|
||||
<div
|
||||
id="search-desktop-wrapper"
|
||||
class="flex flex-row gap-2 items-center"
|
||||
>
|
||||
<input
|
||||
id="search-desktop"
|
||||
type="text"
|
||||
name="search"
|
||||
aria-label="search"
|
||||
class="w-full mx-1 my-3 px-3 py-2 text-md rounded-xl border-2 shadow-sm border-white placeholder-shown:border-white [&:not(:placeholder-shown)]:border-yellow-200"
|
||||
hx-preserve
|
||||
value="{{ search|default('', true) }}"
|
||||
placeholder="search"
|
||||
hx-trigger="input changed delay:300ms"
|
||||
hx-target="#main-panel"
|
||||
|
||||
hx-select="{{hx_select}}, #search-mobile-wrapper, #search-desktop-wrapper"
|
||||
hx-get="{{ (current_local_href ~ {'search': None}|qs)|host}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
hx-headers='{"X-Origin":"search-desktop", "X-Search":"true"}'
|
||||
hx-sync="this:replace"
|
||||
|
||||
autocomplete="off"
|
||||
>
|
||||
|
||||
<div
|
||||
id="search-count-desktop"
|
||||
aria-label="search count"
|
||||
{% if not search_count %}
|
||||
class="text-xl text-red-500"
|
||||
{% endif %}
|
||||
>
|
||||
{% if search %}
|
||||
{{search_count}}
|
||||
{% endif %}
|
||||
{{zap_filter}}
|
||||
</div>
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
24
shared/browser/templates/macros/stickers.html
Normal file
24
shared/browser/templates/macros/stickers.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% macro sticker(src, title, enabled, size=40, found=false) -%}
|
||||
|
||||
<span class="relative inline-flex items-center justify-center group"
|
||||
tabindex="0" aria-label="{{ title|capitalize }}">
|
||||
<!-- sticker icon -->
|
||||
<img
|
||||
src="{{ src }}"
|
||||
width="{{size}}" height="{{size}}"
|
||||
alt="{{ title|capitalize }}"
|
||||
title="{{ title|capitalize }}"
|
||||
class="{% if found %}border-2 border-yellow-200 bg-yellow-300{% endif %} {%if enabled %} opacity-100 {% else %} opacity-40 saturate-0 {% endif %}"
|
||||
/>
|
||||
|
||||
<!-- tooltip -->
|
||||
<span role="tooltip"
|
||||
class="pointer-events-none absolute z-50 bottom-full left-1/2 -translate-x-1/2 mb-2 hidden group-hover/tt:block group-focus-visible/tt:block whitespace-nowrap rounded-md bg-stone-900 text-white text-xs px-2 py-1 shadow-lg">
|
||||
{{ title|capitalize if title|lower != 'sugarfree' else 'Sugar' }}
|
||||
<!-- little arrow -->
|
||||
<span class="absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent border-t-stone-900"></span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
{%- endmacro -%}
|
||||
|
||||
10
shared/browser/templates/macros/title.html
Normal file
10
shared/browser/templates/macros/title.html
Normal file
@@ -0,0 +1,10 @@
|
||||
{% macro title(_class='') %}
|
||||
<a
|
||||
href="{{ blog_url('/') }}"
|
||||
class="{{_class}}"
|
||||
>
|
||||
<h1>
|
||||
{{ site().title }}
|
||||
</h1>
|
||||
</a>
|
||||
{% endmacro %}
|
||||
Reference in New Issue
Block a user