Packages

phoenix_kit

1.6.16
1.7.209 1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib phoenix_kit_web controllers blog_html show.html.heex
Raw

lib/phoenix_kit_web/controllers/blog_html/show.html.heex

<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
page_title={@page_title}
current_path={@conn.request_path}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
>
<%!-- TODO: Add SEO meta tags (description, canonical) when layout system supports meta_tags slot --%>
<article class="blog-post-container max-w-4xl mx-auto px-6 py-8">
<%!-- Breadcrumb Navigation --%>
<div class="breadcrumbs text-sm mb-6">
<ul>
<%= for breadcrumb <- @breadcrumbs do %>
<li>
<%= if breadcrumb.url do %>
<a href={breadcrumb.url}>{breadcrumb.label}</a>
<% else %>
{breadcrumb.label}
<% end %>
</li>
<% end %>
</ul>
</div>
<%!-- Post Header --%>
<header class="mb-8 border-b pb-6">
<div class="flex items-center gap-2 text-sm text-base-content/70">
<%!-- Publication Date --%>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
<time datetime={@post.metadata.published_at}>
{format_date(@post.metadata.published_at)}
</time>
</div>
<%!-- Language Switcher Dropdown --%>
<%= if length(@translations) > 1 do %>
<% current_translation = Enum.find(@translations, & &1.current) %>
<div class="mt-4 flex items-center gap-2">
<div class="dropdown dropdown-end" id="public-language-switcher">
<label tabindex="0" class="btn btn-sm btn-outline shadow-none gap-2">
<span class="text-lg">{current_translation[:flag] || ""}</span>
<span>{current_translation[:name] || @current_language}</span>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 9l-7 7-7-7"
/>
</svg>
</label>
<div
tabindex="0"
class="dropdown-content z-50 shadow-lg bg-base-100 rounded-box w-72 border border-base-300 overflow-hidden"
>
<div class="relative">
<div
id="public-lang-dropdown-list"
class="flex flex-col p-2 max-h-80 overflow-y-auto overflow-x-hidden"
>
<%= for translation <- @translations do %>
<%= if translation.current do %>
<span class="flex items-center gap-2 px-3 py-2 rounded-lg bg-primary/10 text-primary w-full">
<span class="text-lg shrink-0">{translation[:flag] || ""}</span>
<span class="flex-1 truncate">{translation.name}</span>
<svg
class="w-4 h-4 text-primary shrink-0"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2.5"
d="M4.5 12.75l6 6 9-13.5"
/>
</svg>
</span>
<% else %>
<a
href={translation.url}
class="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-base-200 transition-colors w-full text-left"
>
<span class="text-lg shrink-0">{translation[:flag] || ""}</span>
<span class="flex-1 truncate">{translation.name}</span>
</a>
<% end %>
<% end %>
</div>
<%!-- Bottom gradient indicator --%>
<div
id="public-lang-dropdown-gradient"
class="pointer-events-none absolute bottom-0 left-0 right-0 h-16 bg-gradient-to-t from-base-100 to-transparent opacity-0 transition-opacity duration-200"
>
</div>
</div>
</div>
</div>
</div>
<script>
(function() {
function updatePublicLangDropdownGradient() {
const list = document.getElementById('public-lang-dropdown-list');
const gradient = document.getElementById('public-lang-dropdown-gradient');
if (list && gradient) {
const isScrollable = list.scrollHeight > list.clientHeight;
const isNotAtEnd = list.scrollTop < (list.scrollHeight - list.clientHeight - 5);
gradient.style.opacity = (isScrollable && isNotAtEnd) ? '1' : '0';
}
}
document.addEventListener('click', function(e) {
if (e.target.closest('#public-language-switcher')) {
setTimeout(updatePublicLangDropdownGradient, 10);
}
});
const list = document.getElementById('public-lang-dropdown-list');
if (list) {
list.addEventListener('scroll', updatePublicLangDropdownGradient, { passive: true });
}
})();
</script>
<% end %>
</header>
<%!-- Post Content --%>
<div class="markdown-content prose prose-lg max-w-none">
<style>
/* GitHub-style markdown CSS */
.markdown-content {
color: oklch(var(--bc));
line-height: 1.6;
}
.markdown-content h1 {
font-size: 2.25rem;
font-weight: 700;
margin-top: 1.5rem;
margin-bottom: 1rem;
border-bottom: 1px solid oklch(var(--bc) / 0.1);
padding-bottom: 0.5rem;
}
.markdown-content h2 {
font-size: 1.875rem;
font-weight: 600;
margin-top: 1.5rem;
margin-bottom: 0.75rem;
}
.markdown-content h3 {
font-size: 1.5rem;
font-weight: 600;
margin-top: 1.25rem;
margin-bottom: 0.5rem;
}
.markdown-content h4 {
font-size: 1.25rem;
font-weight: 600;
margin-top: 1rem;
margin-bottom: 0.5rem;
}
.markdown-content h5 {
font-size: 1.125rem;
font-weight: 600;
margin-top: 1rem;
margin-bottom: 0.5rem;
}
.markdown-content h6 {
font-size: 1rem;
font-weight: 600;
margin-top: 1rem;
margin-bottom: 0.5rem;
}
.markdown-content p {
margin: 1rem 0;
line-height: 1.7;
}
.markdown-content strong {
font-weight: 700;
}
.markdown-content em {
font-style: italic;
}
.markdown-content del,
.markdown-content s {
text-decoration: line-through;
opacity: 0.7;
}
.markdown-content hr {
border: 0;
border-top: 2px solid oklch(var(--bc) / 0.1);
margin: 2rem 0;
}
.markdown-content code {
background-color: oklch(var(--b2));
padding: 0.2em 0.4em;
border-radius: 0.25rem;
font-size: 0.9em;
font-family: 'Monaco', 'Courier New', monospace;
}
.markdown-content pre {
background: oklch(var(--b3));
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
margin: 1rem 0;
}
.markdown-content pre code {
background: transparent;
padding: 0;
}
.markdown-content a {
color: oklch(var(--p));
text-decoration: underline;
}
.markdown-content a:hover {
color: oklch(var(--pf));
}
.markdown-content blockquote {
border-left: 4px solid oklch(var(--p));
padding-left: 1rem;
margin: 1rem 0;
color: oklch(var(--bc) / 0.7);
font-style: italic;
}
.markdown-content table {
width: 100%;
border-collapse: collapse;
margin: 1rem 0;
}
.markdown-content table th,
.markdown-content table td {
border: 1px solid oklch(var(--bc) / 0.2);
padding: 0.5rem;
text-align: left;
}
.markdown-content table th {
background: oklch(var(--b2));
font-weight: 600;
}
.markdown-content img {
max-width: 100%;
height: auto;
border-radius: 0.5rem;
margin: 1rem 0;
}
.markdown-content ul,
.markdown-content ol {
margin: 1rem 0;
padding-left: 2rem;
}
.markdown-content li {
margin: 0.5rem 0;
}
.markdown-content input[type="checkbox"] {
margin-right: 0.5rem;
cursor: pointer;
}
.markdown-content li:has(input[type="checkbox"]) {
list-style: none;
margin-left: -1.5rem;
}
</style>
{raw(@html_content)}
</div>
<%!-- Post Footer --%>
<footer class="mt-12 pt-6 border-t">
<a href={blog_listing_path(@current_language, @blog_slug)} class="btn btn-outline btn-sm">
← Back to {String.capitalize(@blog_slug)}
</a>
</footer>
</article>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>