feat: Minimal posting form
This commit is contained in:
parent
7894049b6c
commit
3a9c23da95
12 changed files with 173 additions and 18 deletions
|
|
@ -7,7 +7,7 @@
|
||||||
- [ ] Unfollowing
|
- [ ] Unfollowing
|
||||||
- [x] Being followed
|
- [x] Being followed
|
||||||
- [x] Accepting follows
|
- [x] Accepting follows
|
||||||
- Approving / declining follows and authorized instance list
|
- [ ] Approving / declining follows and authorized instance list
|
||||||
- [ ] Liking
|
- [ ] Liking
|
||||||
- [ ] Unliking
|
- [ ] Unliking
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
- [ ] Posting
|
- [ ] Posting
|
||||||
- [ ] Timeline
|
- [ ] Timeline
|
||||||
- [ ] Deleting posts
|
- [ ] Deleting posts
|
||||||
- [ ] Following
|
- [~] Following
|
||||||
- [ ] Unfollowing
|
- [ ] Unfollowing
|
||||||
- [ ] Being followed
|
- [ ] Being followed
|
||||||
- [ ] Accepting follows
|
- [ ] Accepting follows
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,40 @@ defmodule Postland.Activities do
|
||||||
alias Postland.Repo
|
alias Postland.Repo
|
||||||
alias Postland.Follows
|
alias Postland.Follows
|
||||||
|
|
||||||
|
def record_markdown_post(markdown) do
|
||||||
|
id = Ecto.UUID.autogenerate()
|
||||||
|
html = Earmark.as_html!(markdown)
|
||||||
|
|
||||||
|
%{
|
||||||
|
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||||
|
"id" => url(~p"/activities/#{id}"),
|
||||||
|
"type" => "Create",
|
||||||
|
"actor" => Postland.my_actor_id(),
|
||||||
|
"object" => [
|
||||||
|
%{
|
||||||
|
"id" => url(~p"/activities/#{id}"),
|
||||||
|
"type" => "Note",
|
||||||
|
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
||||||
|
"attributedTo" => Postland.my_actor_id(),
|
||||||
|
"mediaType" => "text/html",
|
||||||
|
"content" => html,
|
||||||
|
"to" => "https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"id" => url(~p"/activities/#{id}"),
|
||||||
|
"type" => "Note",
|
||||||
|
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
||||||
|
"attributedTo" => Postland.my_actor_id(),
|
||||||
|
"mediaType" => "text/markdown",
|
||||||
|
"content" => markdown,
|
||||||
|
"to" => "https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|> Postland.Activity.changeset()
|
||||||
|
|> Repo.insert()
|
||||||
|
end
|
||||||
|
|
||||||
def process_activity(params) do
|
def process_activity(params) do
|
||||||
case record_activity(params) do
|
case record_activity(params) do
|
||||||
{:ok, activity} ->
|
{:ok, activity} ->
|
||||||
|
|
@ -23,10 +57,14 @@ defmodule Postland.Activities do
|
||||||
|> Repo.insert()
|
|> Repo.insert()
|
||||||
end
|
end
|
||||||
|
|
||||||
def cause_effects(%Activity{type: "Accept", data: %{"object" => %{"type" => "Follow", "id" => follow_id}}} = activity) do
|
def cause_effects(
|
||||||
|
%Activity{type: "Accept", data: %{"object" => %{"type" => "Follow", "id" => follow_id}}} =
|
||||||
|
activity
|
||||||
|
) do
|
||||||
pattern = ~r|/follows/([^/]+)|
|
pattern = ~r|/follows/([^/]+)|
|
||||||
|
|
||||||
actor_id = case Regex.run(pattern, follow_id) do
|
actor_id =
|
||||||
|
case Regex.run(pattern, follow_id) do
|
||||||
[_, encoded_actor_id] ->
|
[_, encoded_actor_id] ->
|
||||||
Base.url_decode64!(encoded_actor_id, padding: false)
|
Base.url_decode64!(encoded_actor_id, padding: false)
|
||||||
|
|
||||||
|
|
@ -47,11 +85,14 @@ defmodule Postland.Activities do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
end
|
end
|
||||||
|
|
||||||
def cause_effects(%Activity{actor_id: actor_id, type: "Follow", data: %{"object" => object}} = activity) do
|
def cause_effects(
|
||||||
|
%Activity{actor_id: actor_id, type: "Follow", data: %{"object" => object}} = activity
|
||||||
|
) do
|
||||||
if object == Postland.my_actor_id() do
|
if object == Postland.my_actor_id() do
|
||||||
case Postland.Follows.record_inbound_request(actor_id) do
|
case Postland.Follows.record_inbound_request(actor_id) do
|
||||||
{:ok, _follow} ->
|
{:ok, _follow} ->
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
|
||||||
other ->
|
other ->
|
||||||
other
|
other
|
||||||
end
|
end
|
||||||
|
|
|
||||||
16
lib/postland/post.ex
Normal file
16
lib/postland/post.ex
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
defmodule Postland.Post do
|
||||||
|
def html_content(activity) do
|
||||||
|
case Map.get(activity.data, "object") do
|
||||||
|
map when is_map(map) ->
|
||||||
|
Map.get(map, "content")
|
||||||
|
|
||||||
|
list when is_list(list) ->
|
||||||
|
Enum.find_value(list, fn map ->
|
||||||
|
Map.get(map, "mediaType") == "text/html" && Map.get(map, "content")
|
||||||
|
end) || ""
|
||||||
|
|
||||||
|
nil ->
|
||||||
|
""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -19,6 +19,64 @@ defmodule PostlandWeb.CoreComponents do
|
||||||
alias Phoenix.LiveView.JS
|
alias Phoenix.LiveView.JS
|
||||||
use Gettext, backend: PostlandWeb.Gettext
|
use Gettext, backend: PostlandWeb.Gettext
|
||||||
|
|
||||||
|
def post_form(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div class="relative">
|
||||||
|
<.form class="py-5" phx-submit="create_post" phx-change="change_post">
|
||||||
|
<div class="flex items-start space-x-4">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<img class="inline-block h-10 w-10 rounded-full" alt="" src="/images/avatar.png" />
|
||||||
|
</div>
|
||||||
|
<div class="min-w-0 flex-1 relative">
|
||||||
|
<div class="overflow-hidden bg-white relative rounded-lg shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-indigo-600">
|
||||||
|
<label for="post-body" class="sr-only">post body</label>
|
||||||
|
<textarea
|
||||||
|
rows="3"
|
||||||
|
name="post"
|
||||||
|
id="post-body"
|
||||||
|
class="block w-full resize-none border-0 bg-transparent py-1.5 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
|
||||||
|
placeholder="post body"
|
||||||
|
></textarea>
|
||||||
|
<!-- Spacer element to match the height of the toolbar -->
|
||||||
|
<div class="py-2" aria-hidden="true">
|
||||||
|
<!-- Matches height of button in toolbar (1px border + 36px content height) -->
|
||||||
|
<div class="py-px">
|
||||||
|
<div class="h-9"></div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute inset-x-0 bottom-0 flex justify-between py-2 pl-3 pr-2">
|
||||||
|
<div class="flex items-center space-x-5"></div>
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
|
||||||
|
>
|
||||||
|
Post
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</.form>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
def post_card(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div class="divide-y divide-gray-200 overflow-hidden rounded-lg bg-white shadow">
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<%= {:safe, Postland.Post.html_content(@post)} %>
|
||||||
|
</div>
|
||||||
|
<div class="px-4 py-4 sm:px-6">
|
||||||
|
<!-- Content goes here -->
|
||||||
|
<!-- We use less vertical padding on card footers at all sizes than on headers or body sections -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Renders a modal.
|
Renders a modal.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-white antialiased">
|
<body class="bg-purple-50 antialiased">
|
||||||
<header class="flex w-full bg-purple-950 text-purple-100 py-4">
|
<header class="flex w-full bg-purple-950 text-purple-100 py-4">
|
||||||
<div class="flex-1 px-4 uppercase font-bold ">
|
<div class="flex-1 px-4 uppercase font-bold ">
|
||||||
Postland
|
Postland
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,11 @@ defmodule PostlandWeb.ActorJSON do
|
||||||
"id" => url(~p"/actor#main-key"),
|
"id" => url(~p"/actor#main-key"),
|
||||||
"owner" => url(~p"/actor"),
|
"owner" => url(~p"/actor"),
|
||||||
"publicKeyPem" => user.public_key
|
"publicKeyPem" => user.public_key
|
||||||
|
},
|
||||||
|
"icon" => %{
|
||||||
|
"type" => "Image",
|
||||||
|
"mediaType" => "image/png",
|
||||||
|
"url" => url(~p"/images/avatar.png")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,11 @@ defmodule PostlandWeb.WebfingerJSON do
|
||||||
"rel" => "http://webfinger.net/rel/profile-page",
|
"rel" => "http://webfinger.net/rel/profile-page",
|
||||||
"type" => "text/html",
|
"type" => "text/html",
|
||||||
"href" => url(~p"/about")
|
"href" => url(~p"/about")
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"rel" => "http://webfinger.net/rel/avatar",
|
||||||
|
"type" => "image/png",
|
||||||
|
"href" => url(~p"/images/avatar.png")
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
28
lib/postland_web/live/timeline_live.ex
Normal file
28
lib/postland_web/live/timeline_live.ex
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
defmodule PostlandWeb.TimelineLive do
|
||||||
|
use PostlandWeb, :live_view
|
||||||
|
|
||||||
|
def render(assigns) do
|
||||||
|
~H"""
|
||||||
|
<.post_form post_content={@post} />
|
||||||
|
<div id="timeline-posts" phx-update="stream">
|
||||||
|
<div :for={{id, post} <- @streams.posts} id={id} class="mt-10">
|
||||||
|
<.post_card post={post} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
def mount(_params, _session, socket) do
|
||||||
|
{:ok, socket |> assign(:post, "") |> stream(:posts, [])}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("create_post", %{"post" => post}, socket) do
|
||||||
|
{:ok, post} = Postland.Activities.record_markdown_post(post)
|
||||||
|
|
||||||
|
{:noreply, socket |> assign(:post, "") |> stream_insert(:posts, post)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("change_post", %{"post" => post}, socket) do
|
||||||
|
{:noreply, socket |> assign(:post, post)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -34,7 +34,6 @@ defmodule PostlandWeb.Router do
|
||||||
scope "/", PostlandWeb do
|
scope "/", PostlandWeb do
|
||||||
pipe_through [:browser, :redirect_if_not_set_up]
|
pipe_through [:browser, :redirect_if_not_set_up]
|
||||||
|
|
||||||
get "/", PageController, :home
|
|
||||||
live "/about", ProfileLive, :show
|
live "/about", ProfileLive, :show
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -79,6 +78,7 @@ defmodule PostlandWeb.Router do
|
||||||
|
|
||||||
live_session :require_authenticated_user,
|
live_session :require_authenticated_user,
|
||||||
on_mount: [{PostlandWeb.UserAuth, :ensure_authenticated}] do
|
on_mount: [{PostlandWeb.UserAuth, :ensure_authenticated}] do
|
||||||
|
live "/", TimelineLive, :show
|
||||||
live "/users/settings", UserSettingsLive, :edit
|
live "/users/settings", UserSettingsLive, :edit
|
||||||
live "/@:acct", OtherProfileLive, :show
|
live "/@:acct", OtherProfileLive, :show
|
||||||
end
|
end
|
||||||
|
|
|
||||||
3
mix.exs
3
mix.exs
|
|
@ -59,7 +59,8 @@ defmodule Postland.MixProject do
|
||||||
{:dns_cluster, "~> 0.1.1"},
|
{:dns_cluster, "~> 0.1.1"},
|
||||||
{:bandit, "~> 1.2"},
|
{:bandit, "~> 1.2"},
|
||||||
{:req, "~> 0.5.6"},
|
{:req, "~> 0.5.6"},
|
||||||
{:stream_data, "~> 1.1.1", only: [:test]}
|
{:stream_data, "~> 1.1.1", only: [:test]},
|
||||||
|
{:earmark, "~> 1.4.47"}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
1
mix.lock
1
mix.lock
|
|
@ -7,6 +7,7 @@
|
||||||
"db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
|
"db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
|
||||||
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
|
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
|
||||||
"dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"},
|
"dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"},
|
||||||
|
"earmark": {:hex, :earmark, "1.4.47", "7e7596b84fe4ebeb8751e14cbaeaf4d7a0237708f2ce43630cfd9065551f94ca", [:mix], [], "hexpm", "3e96bebea2c2d95f3b346a7ff22285bc68a99fbabdad9b655aa9c6be06c698f8"},
|
||||||
"ecto": {:hex, :ecto, "3.12.3", "1a9111560731f6c3606924c81c870a68a34c819f6d4f03822f370ea31a582208", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9efd91506ae722f95e48dc49e70d0cb632ede3b7a23896252a60a14ac6d59165"},
|
"ecto": {:hex, :ecto, "3.12.3", "1a9111560731f6c3606924c81c870a68a34c819f6d4f03822f370ea31a582208", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9efd91506ae722f95e48dc49e70d0cb632ede3b7a23896252a60a14ac6d59165"},
|
||||||
"ecto_sql": {:hex, :ecto_sql, "3.12.0", "73cea17edfa54bde76ee8561b30d29ea08f630959685006d9c6e7d1e59113b7d", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dc9e4d206f274f3947e96142a8fdc5f69a2a6a9abb4649ef5c882323b6d512f0"},
|
"ecto_sql": {:hex, :ecto_sql, "3.12.0", "73cea17edfa54bde76ee8561b30d29ea08f630959685006d9c6e7d1e59113b7d", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dc9e4d206f274f3947e96142a8fdc5f69a2a6a9abb4649ef5c882323b6d512f0"},
|
||||||
"ecto_sqlite3": {:hex, :ecto_sqlite3, "0.17.2", "200226e057f76c40be55fbac77771eb1a233260ab8ec7283f5da6d9402bde8de", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.12", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:exqlite, "~> 0.22", [hex: :exqlite, repo: "hexpm", optional: false]}], "hexpm", "a3838919c5a34c268c28cafab87b910bcda354a9a4e778658da46c149bb2c1da"},
|
"ecto_sqlite3": {:hex, :ecto_sqlite3, "0.17.2", "200226e057f76c40be55fbac77771eb1a233260ab8ec7283f5da6d9402bde8de", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.12", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:exqlite, "~> 0.22", [hex: :exqlite, repo: "hexpm", optional: false]}], "hexpm", "a3838919c5a34c268c28cafab87b910bcda354a9a4e778658da46c149bb2c1da"},
|
||||||
|
|
|
||||||
BIN
priv/static/images/avatar.png
Normal file
BIN
priv/static/images/avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Loading…
Reference in a new issue