diff --git a/README.md b/README.md index fbedbf7..f4b0eda 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/lib/postland/activities.ex b/lib/postland/activities.ex index e97b4b3..8589617 100644 --- a/lib/postland/activities.ex +++ b/lib/postland/activities.ex @@ -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 diff --git a/lib/postland/post.ex b/lib/postland/post.ex new file mode 100644 index 0000000..a00ea48 --- /dev/null +++ b/lib/postland/post.ex @@ -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 diff --git a/lib/postland_web/components/core_components.ex b/lib/postland_web/components/core_components.ex index 1b58fae..4915bca 100644 --- a/lib/postland_web/components/core_components.ex +++ b/lib/postland_web/components/core_components.ex @@ -19,6 +19,64 @@ defmodule PostlandWeb.CoreComponents do alias Phoenix.LiveView.JS use Gettext, backend: PostlandWeb.Gettext + def post_form(assigns) do + ~H""" +
+ <.form class="py-5" phx-submit="create_post" phx-change="change_post"> +
+
+ +
+
+
+ + + + +
+
+
+ +
+ """ + end + + def post_card(assigns) do + ~H""" +
+
+ <%= {:safe, Postland.Post.html_content(@post)} %> +
+
+ + +
+
+ """ + end + @doc """ Renders a modal. diff --git a/lib/postland_web/components/layouts/root.html.heex b/lib/postland_web/components/layouts/root.html.heex index e08cef8..36e26cc 100644 --- a/lib/postland_web/components/layouts/root.html.heex +++ b/lib/postland_web/components/layouts/root.html.heex @@ -11,7 +11,7 @@ - +
Postland diff --git a/lib/postland_web/controllers/actor_json.ex b/lib/postland_web/controllers/actor_json.ex index ad0e7e9..6db93ce 100644 --- a/lib/postland_web/controllers/actor_json.ex +++ b/lib/postland_web/controllers/actor_json.ex @@ -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 diff --git a/lib/postland_web/controllers/webfinger_json.ex b/lib/postland_web/controllers/webfinger_json.ex index e4dbb47..6e55d8d 100644 --- a/lib/postland_web/controllers/webfinger_json.ex +++ b/lib/postland_web/controllers/webfinger_json.ex @@ -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") } ] } diff --git a/lib/postland_web/live/timeline_live.ex b/lib/postland_web/live/timeline_live.ex new file mode 100644 index 0000000..679e20e --- /dev/null +++ b/lib/postland_web/live/timeline_live.ex @@ -0,0 +1,28 @@ +defmodule PostlandWeb.TimelineLive do + use PostlandWeb, :live_view + + def render(assigns) do + ~H""" + <.post_form post_content={@post} /> +
+
+ <.post_card post={post} /> +
+
+ """ + 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 diff --git a/lib/postland_web/router.ex b/lib/postland_web/router.ex index 8acd2f9..1d03462 100644 --- a/lib/postland_web/router.ex +++ b/lib/postland_web/router.ex @@ -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 diff --git a/mix.exs b/mix.exs index 0d08d14..5d274f3 100644 --- a/mix.exs +++ b/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 diff --git a/mix.lock b/mix.lock index 6753c6c..2aa4117 100644 --- a/mix.lock +++ b/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"}, diff --git a/priv/static/images/avatar.png b/priv/static/images/avatar.png new file mode 100644 index 0000000..e9a19a9 Binary files /dev/null and b/priv/static/images/avatar.png differ