Skip to content
Merged
9 changes: 8 additions & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ config :logger, :console,
:path,
:blog,
:pattern,
:content_size
:content_size,
:error
]

# For development/testing with real SMTP (when available)
Expand All @@ -53,3 +54,9 @@ config :logger, :console,
# password: System.get_env("SMTP_PASSWORD"),
# tls: :if_available,
# retries: 1

# Import environment-specific config
# This allows config/test.exs to override settings for test environment
if File.exists?("#{__DIR__}/#{Mix.env()}.exs") do
import_config "#{Mix.env()}.exs"
end
44 changes: 44 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Config

# Configure test environment for PhoenixKit
# This file is imported by config.exs when Mix.env() == :test

# Configure test database (when PhoenixKit is used in parent applications)
# Parent apps should configure their own test repo here
# config :phoenix_kit,
# repo: MyApp.Repo

# Configure test mailer - use Local adapter for test environment
config :phoenix_kit, PhoenixKit.Mailer, adapter: Swoosh.Adapters.Test

# Disable Swoosh API client as it is only required for production adapters
config :swoosh, :api_client, false

# Configure Hammer rate limiting for tests
# Use test-friendly limits that match test expectations
config :hammer,
backend: {Hammer.Backend.ETS, [expiry_ms: 60_000, cleanup_interval_ms: 60_000]}

config :phoenix_kit, PhoenixKit.Users.RateLimiter,
login_limit: 5,
login_window_ms: 60_000,
magic_link_limit: 3,
magic_link_window_ms: 300_000,
password_reset_limit: 3,
password_reset_window_ms: 300_000,
registration_limit: 3,
registration_window_ms: 3_600_000,
registration_ip_limit: 10,
registration_ip_window_ms: 3_600_000

# Configure session fingerprinting for tests
config :phoenix_kit,
session_fingerprint_enabled: true,
session_fingerprint_strict: false

# Future: Configure FakeSettings when blogging tests are implemented
# config :phoenix_kit,
# blogging_settings_module: PhoenixKit.Test.FakeSettings

# Configure logger for tests
config :logger, level: :warning
20 changes: 20 additions & 0 deletions guides/phk_blogging_format.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Only a subset is required, but the blogging UI will populate everything shown ab
- `title` – displayed in admin tables and public templates.
- `status` – controls whether the post is discoverable publicly (`published` only).
- `published_at` – timestamp used for ordering and for timestamp-mode folders.
- `featured_image_id` – optional PhoenixKit Storage file ID used for the public listing thumbnail.
- `created_by_* / updated_by_*` – audit metadata; the editor manages these.

---
Expand Down Expand Up @@ -81,6 +82,7 @@ You can mix **bold text** and inline components:
| `<Headline>…</Headline>` | Renders a hero-style heading. |
| `<Subheadline>…</Subheadline>` | Medium-sized supporting text. |
| `<CTA primary="true|false" action="/path-or-anchor">Label</CTA>` | Button styled by the admin theme. |
| `<Video …>Caption</Video>` | Responsive YouTube embeds. Provide either `video_id="dQw4w9WgXcQ"` or a `url="https://youtu.be/dQw4w9WgXcQ"`. Optional attributes: `autoplay`, `muted`, `controls`, `loop`, `start` (seconds), and `ratio` (`16:9`, `4:3`, `1:1`, `21:9`). Use the component body (or `caption="..."` when self-closing) to show a caption. |

Additional components can be introduced by adding Phoenix components under `lib/phoenix_kit_web/components/blogging/` and registering them in the PageBuilder renderer.

Expand Down Expand Up @@ -189,6 +191,24 @@ published_at: 2025-07-01T10:00:00Z

---

## Example – embedding a YouTube video

```markdown
## Watch the launch recap

<Video
url="https://youtu.be/dQw4w9WgXcQ"
autoplay="false"
muted="false"
ratio="16:9"
start="42"
>
Highlights from our community livestream.
</Video>
```

---

## Rendering pipeline (current behaviour)

1. **Frontmatter parsing** – YAML is parsed to capture metadata.
Expand Down
49 changes: 33 additions & 16 deletions lib/phoenix_kit/blogging/renderer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ defmodule PhoenixKit.Blogging.Renderer do

require Logger

alias Phoenix.HTML.Safe
alias PhoenixKitWeb.Components.Blogging.Image
alias PhoenixKitWeb.Components.Blogging.Video
alias PhoenixKitWeb.Live.Modules.Blogging.PageBuilder

@cache_name :blog_posts
@cache_version "v1"
@component_regex ~r/<(Image|Hero|CTA|Headline|Subheadline)\s+([^>]*?)\/>/s
@component_regex ~r/<(Image|Hero|CTA|Headline|Subheadline|Video)\s+([^>]*?)\/>/s

@doc """
Renders a post's markdown content to HTML.
Expand Down Expand Up @@ -55,7 +60,7 @@ defmodule PhoenixKit.Blogging.Renderer do
{time, result} =
:timer.tc(fn ->
cond do
is_pure_phk_content?(content) ->
pure_phk_content?(content) ->
render_phk_content(content)

has_embedded_components?(content) ->
Expand All @@ -73,7 +78,7 @@ defmodule PhoenixKit.Blogging.Renderer do
def render_markdown(_), do: ""

# Detect if content is pure .phk XML format (starts with <Page> or <Hero>)
defp is_pure_phk_content?(content) do
defp pure_phk_content?(content) do
trimmed = String.trim(content)
String.starts_with?(trimmed, "<Page") || String.starts_with?(trimmed, "<Hero")
end
Expand All @@ -87,11 +92,11 @@ defmodule PhoenixKit.Blogging.Renderer do

# Render .phk content using PageBuilder
defp render_phk_content(content) do
case PhoenixKitWeb.Live.Modules.Blogging.PageBuilder.render_content(content) do
case PageBuilder.render_content(content) do
{:ok, html} ->
# Convert Phoenix.LiveView.Rendered to string
html
|> Phoenix.HTML.Safe.to_iodata()
|> Safe.to_iodata()
|> IO.iodata_to_binary()

{:error, reason} ->
Expand Down Expand Up @@ -120,8 +125,6 @@ defmodule PhoenixKit.Blogging.Renderer do
Regex.replace(~r/^[ \t]+(?=#)/m, content, "")
end

defp normalize_markdown(content), do: content

# Render mixed content: markdown with embedded XML components
defp render_mixed_content(content) when content == "" or is_nil(content), do: ""

Expand Down Expand Up @@ -178,21 +181,35 @@ defmodule PhoenixKit.Blogging.Renderer do
children: []
}

case PhoenixKitWeb.Components.Blogging.Image.render(assigns) do
rendered when is_struct(rendered) ->
rendered
|> Phoenix.HTML.Safe.to_iodata()
|> IO.iodata_to_binary()

html when is_binary(html) ->
html
end
Image.render(assigns)
|> Safe.to_iodata()
|> IO.iodata_to_binary()
rescue
error ->
Logger.warning("Error rendering Image component: #{inspect(error)}")
"<div class='error'>Error rendering image</div>"
end

defp render_inline_component("Video", attrs) do
attr_map = parse_xml_attributes(attrs)

assigns = %{
__changed__: nil,
attributes: attr_map,
variant: Map.get(attr_map, "variant", "default"),
content: Map.get(attr_map, "caption"),
children: []
}

Video.render(assigns)
|> Safe.to_iodata()
|> IO.iodata_to_binary()
rescue
error ->
Logger.warning("Error rendering Video component: #{inspect(error)}")
"<div class='error'>Error rendering video</div>"
end

defp render_inline_component(tag, _attrs) do
# Fallback for other components
Logger.warning("Inline component not supported yet: #{tag}")
Expand Down
2 changes: 1 addition & 1 deletion lib/phoenix_kit/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ defmodule PhoenixKit.Config do
# This function is used as a fallback when explicit `:parent_app_name` configuration
# is not provided. It enables PhoenixKit to automatically integrate with parent
# applications without requiring additional configuration in most cases.
defp get_parent_app_fallback() do
defp get_parent_app_fallback do
# Get the application of the configured repo to determine parent app
case get(:repo) do
{:ok, repo_module} when is_atom(repo_module) ->
Expand Down
4 changes: 2 additions & 2 deletions lib/phoenix_kit/install/basic_configuration.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ defmodule PhoenixKit.Install.BasicConfiguration do
|> Config.configure_new(
"config.exs",
:phoenix_kit,
:parent_app_name,
[:parent_app_name],
parent_app_name
)
|> Config.configure_new(
"config.exs",
:phoenix_kit,
:parent_module,
[:parent_module],
parent_module
)
end
Expand Down
40 changes: 19 additions & 21 deletions lib/phoenix_kit/install/repo_detection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -151,27 +151,25 @@ defmodule PhoenixKit.Install.RepoDetection do

# Add repo configuration to config files
defp add_repo_config_to_files(igniter, repo_module) do
try do
igniter
# Add repo config to main config.exs
|> Config.configure_new(
"config.exs",
:phoenix_kit,
[:repo],
repo_module
)
# Also add repo config to test.exs for testing
|> Config.configure_new(
"test.exs",
:phoenix_kit,
[:repo],
repo_module
)
rescue
_ ->
# Fallback to simple file operations
add_repo_config_simple(igniter, repo_module)
end
igniter
# Add repo config to main config.exs
|> Config.configure_new(
"config.exs",
:phoenix_kit,
[:repo],
repo_module
)
# Also add repo config to test.exs for testing
|> Config.configure_new(
"test.exs",
:phoenix_kit,
[:repo],
repo_module
)
rescue
_ ->
# Fallback to simple file operations
add_repo_config_simple(igniter, repo_module)
end

# Simple file append for repo configuration when Igniter fails
Expand Down
8 changes: 3 additions & 5 deletions lib/phoenix_kit/storage.ex
Original file line number Diff line number Diff line change
Expand Up @@ -804,11 +804,9 @@ defmodule PhoenixKit.Storage do
end

defp signed_file_url(file_id, variant_name) do
try do
URLSigner.signed_url(file_id, variant_name, locale: :none)
rescue
_ -> nil
end
URLSigner.signed_url(file_id, variant_name, locale: :none)
rescue
_ -> nil
end

@doc """
Expand Down
4 changes: 3 additions & 1 deletion lib/phoenix_kit_web/components/blogging/hero.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ defmodule PhoenixKitWeb.Components.Blogging.Hero do
"""
use Phoenix.Component

alias PhoenixKitWeb.Live.Modules.Blogging.PageBuilder.Renderer

attr :variant, :string, default: "centered"
attr :children, :list, default: []
attr :attributes, :map, default: %{}
Expand Down Expand Up @@ -91,7 +93,7 @@ defmodule PhoenixKitWeb.Components.Blogging.Hero do
end

defp render_child(child, assigns) do
case PhoenixKitWeb.Live.Modules.Blogging.PageBuilder.Renderer.render(child, assigns) do
case Renderer.render(child, assigns) do
{:ok, html} -> html
{:error, _} -> ""
end
Expand Down
4 changes: 3 additions & 1 deletion lib/phoenix_kit_web/components/blogging/page.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ defmodule PhoenixKitWeb.Components.Blogging.Page do
"""
use Phoenix.Component

alias PhoenixKitWeb.Live.Modules.Blogging.PageBuilder.Renderer

attr :children, :list, default: []
attr :attributes, :map, default: %{}
attr :variant, :string, default: "default"
Expand All @@ -19,7 +21,7 @@ defmodule PhoenixKitWeb.Components.Blogging.Page do
end

defp render_child(child, assigns) do
case PhoenixKitWeb.Live.Modules.Blogging.PageBuilder.Renderer.render(child, assigns) do
case Renderer.render(child, assigns) do
{:ok, html} -> html
{:error, _} -> ""
end
Expand Down
Loading
Loading