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
|
||||
- [x] Being followed
|
||||
- [x] Accepting follows
|
||||
- Approving / declining follows and authorized instance list
|
||||
- [ ] Approving / declining follows and authorized instance list
|
||||
- [ ] Liking
|
||||
- [ ] Unliking
|
||||
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
- [ ] Posting
|
||||
- [ ] Timeline
|
||||
- [ ] Deleting posts
|
||||
- [ ] Following
|
||||
- [~] Following
|
||||
- [ ] Unfollowing
|
||||
- [ ] Being followed
|
||||
- [ ] Accepting follows
|
||||
|
|
|
|||
|
|
@ -7,13 +7,47 @@ defmodule Postland.Activities do
|
|||
alias Postland.Repo
|
||||
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
|
||||
case record_activity(params) do
|
||||
{:ok, activity} ->
|
||||
cause_effects(activity)
|
||||
|
||||
other ->
|
||||
other
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -23,16 +57,20 @@ defmodule Postland.Activities do
|
|||
|> Repo.insert()
|
||||
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/([^/]+)|
|
||||
|
||||
actor_id = case Regex.run(pattern, follow_id) do
|
||||
[_, encoded_actor_id] ->
|
||||
Base.url_decode64!(encoded_actor_id, padding: false)
|
||||
actor_id =
|
||||
case Regex.run(pattern, follow_id) do
|
||||
[_, encoded_actor_id] ->
|
||||
Base.url_decode64!(encoded_actor_id, padding: false)
|
||||
|
||||
_other ->
|
||||
nil
|
||||
end
|
||||
_other ->
|
||||
nil
|
||||
end
|
||||
|
||||
case Follows.get(Postland.my_actor_id(), actor_id) do
|
||||
nil ->
|
||||
|
|
@ -44,14 +82,17 @@ defmodule Postland.Activities do
|
|||
Follows.confirm_request(request)
|
||||
end
|
||||
|
||||
{:ok, activity}
|
||||
{:ok, activity}
|
||||
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
|
||||
case Postland.Follows.record_inbound_request(actor_id) do
|
||||
{:ok, _follow} ->
|
||||
{:ok, activity}
|
||||
|
||||
other ->
|
||||
other
|
||||
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
|
||||
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 """
|
||||
Renders a modal.
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
||||
</script>
|
||||
</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">
|
||||
<div class="flex-1 px-4 uppercase font-bold ">
|
||||
Postland
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@ defmodule PostlandWeb.ActorJSON do
|
|||
"id" => url(~p"/actor#main-key"),
|
||||
"owner" => url(~p"/actor"),
|
||||
"publicKeyPem" => user.public_key
|
||||
},
|
||||
"icon" => %{
|
||||
"type" => "Image",
|
||||
"mediaType" => "image/png",
|
||||
"url" => url(~p"/images/avatar.png")
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -16,8 +16,13 @@ defmodule PostlandWeb.WebfingerJSON do
|
|||
},
|
||||
%{
|
||||
"rel" => "http://webfinger.net/rel/profile-page",
|
||||
"type" =>"text/html",
|
||||
"href" => url(~p"/about")
|
||||
"type" => "text/html",
|
||||
"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
|
||||
pipe_through [:browser, :redirect_if_not_set_up]
|
||||
|
||||
get "/", PageController, :home
|
||||
live "/about", ProfileLive, :show
|
||||
end
|
||||
|
||||
|
|
@ -79,6 +78,7 @@ defmodule PostlandWeb.Router do
|
|||
|
||||
live_session :require_authenticated_user,
|
||||
on_mount: [{PostlandWeb.UserAuth, :ensure_authenticated}] do
|
||||
live "/", TimelineLive, :show
|
||||
live "/users/settings", UserSettingsLive, :edit
|
||||
live "/@:acct", OtherProfileLive, :show
|
||||
end
|
||||
|
|
|
|||
3
mix.exs
3
mix.exs
|
|
@ -59,7 +59,8 @@ defmodule Postland.MixProject do
|
|||
{:dns_cluster, "~> 0.1.1"},
|
||||
{:bandit, "~> 1.2"},
|
||||
{:req, "~> 0.5.6"},
|
||||
{:stream_data, "~> 1.1.1", only: [:test]}
|
||||
{:stream_data, "~> 1.1.1", only: [:test]},
|
||||
{:earmark, "~> 1.4.47"}
|
||||
]
|
||||
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"},
|
||||
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
|
||||
"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_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"},
|
||||
|
|
|
|||
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