Packages

phoenix_kit

1.5.0
1.7.210 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 live modules blogging context metadata.ex
Raw

lib/phoenix_kit_web/live/modules/blogging/context/metadata.ex

defmodule PhoenixKitWeb.Live.Modules.Blogging.Metadata do
@moduledoc """
Metadata helpers for .phk (PhoenixKit) blogging posts.
Metadata is stored as a simple key-value format at the top of the file:
```
---
slug: home
title: Welcome
status: published
published_at: 2025-10-29T18:48:00Z
---
Content goes here...
```
"""
@type metadata :: %{
status: String.t(),
title: String.t(),
description: String.t() | nil,
slug: String.t(),
published_at: String.t(),
created_at: String.t() | nil,
created_by_id: String.t() | nil,
created_by_email: String.t() | nil,
updated_by_id: String.t() | nil,
updated_by_email: String.t() | nil
}
@doc """
Parses .phk content, extracting metadata from frontmatter and returning the content.
"""
@spec parse_with_content(String.t()) :: {:ok, metadata(), String.t()} | {:error, atom()}
def parse_with_content(content) do
case extract_frontmatter(content) do
{:ok, metadata, body_content} ->
{:ok, metadata, body_content}
{:error, _} ->
# Fallback: try old XML format for backwards compatibility
metadata = extract_metadata_from_xml(content)
{:ok, metadata, content}
end
end
@doc """
Serializes metadata as YAML-style frontmatter.
"""
@spec serialize(metadata()) :: String.t()
def serialize(metadata) do
optional_lines =
[:created_at, :created_by_id, :created_by_email, :updated_by_id, :updated_by_email]
|> Enum.flat_map(fn key ->
case metadata_value(metadata, key) do
nil -> []
"" -> []
value -> ["#{Atom.to_string(key)}: #{value}"]
end
end)
lines =
[
"slug: #{metadata.slug}",
"title: #{metadata.title || ""}",
"status: #{metadata.status}",
"published_at: #{metadata.published_at}"
]
|> Enum.concat(optional_lines)
|> Enum.join("\n")
"""
---
#{lines}
---
"""
end
@doc """
Returns default metadata for a new post.
"""
@spec default_metadata() :: metadata()
def default_metadata do
now = DateTime.utc_now() |> DateTime.truncate(:second)
%{
status: "draft",
title: "",
description: nil,
slug: "",
published_at: DateTime.to_iso8601(now),
created_at: nil,
created_by_id: nil,
created_by_email: nil,
updated_by_id: nil,
updated_by_email: nil
}
end
# Extract metadata from YAML-style frontmatter
defp extract_frontmatter(content) do
case Regex.run(~r/^---\n(.*?)\n---\n(.*)$/s, content) do
[_, frontmatter, body] ->
metadata = parse_frontmatter_lines(frontmatter)
{:ok, metadata, String.trim(body)}
_ ->
{:error, :no_frontmatter}
end
end
defp parse_frontmatter_lines(frontmatter) do
default = default_metadata()
lines =
frontmatter
|> String.split("\n")
|> Enum.map(&String.trim/1)
|> Enum.reject(&(&1 == ""))
metadata =
Enum.reduce(lines, %{}, fn line, acc ->
case String.split(line, ":", parts: 2) do
[key, value] ->
Map.put(acc, String.trim(key), String.trim(value))
_ ->
acc
end
end)
metadata_map = %{
title: Map.get(metadata, "title", default.title),
status: Map.get(metadata, "status", default.status),
slug: Map.get(metadata, "slug", default.slug),
published_at: Map.get(metadata, "published_at", default.published_at),
description: Map.get(metadata, "description"),
created_at: Map.get(metadata, "created_at", default.created_at),
created_by_id: Map.get(metadata, "created_by_id", default.created_by_id),
created_by_email: Map.get(metadata, "created_by_email", default.created_by_email),
updated_by_id: Map.get(metadata, "updated_by_id", default.updated_by_id),
updated_by_email: Map.get(metadata, "updated_by_email", default.updated_by_email)
}
metadata_map
end
# Extract metadata from <Page> element attributes (legacy XML format)
defp extract_metadata_from_xml(content) do
default = default_metadata()
# Simple regex-based extraction (for now)
title = extract_attribute(content, "title") || default.title
status = extract_attribute(content, "status") || default.status
slug = extract_attribute(content, "slug") || default.slug
published_at = extract_attribute(content, "published_at") || default.published_at
description = extract_attribute(content, "description")
%{
title: title,
status: status,
slug: slug,
published_at: published_at,
description: description,
created_at: nil,
created_by_id: nil,
created_by_email: nil,
updated_by_id: nil,
updated_by_email: nil
}
end
defp extract_attribute(content, attr_name) do
regex = ~r/<Page[^>]*\s#{attr_name}="([^"]*)"/
case Regex.run(regex, content) do
[_, value] -> value
_ -> nil
end
end
defp metadata_value(metadata, key) do
Map.get(metadata, key) || Map.get(metadata, Atom.to_string(key))
end
end