diff --git a/README.md b/README.md index ba419da..0bdd2c5 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,9 @@ # SomethingErlang -To start your Phoenix server: +Up and running: - * Install dependencies with `mix deps.get` - * Create and migrate your database with `mix ecto.setup` - * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` + * `mix deps.get` + * `mix ecto.setup` + * `mix phx.server` Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. - -Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). - -## Learn more - - * Official website: https://www.phoenixframework.org/ - * Guides: https://hexdocs.pm/phoenix/overview.html - * Docs: https://hexdocs.pm/phoenix - * Forum: https://elixirforum.com/c/phoenix-forum - * Source: https://github.com/phoenixframework/phoenix diff --git a/assets/css/app.css b/assets/css/app.css index 4cccce3..2b10b32 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -42,7 +42,7 @@ body { } .pagination i { - @apply h-5 px-1; + @apply h-5; } /* Alerts and form errors used by phx.new */ diff --git a/config/config.exs b/config/config.exs index 1c625c6..6f933ca 100644 --- a/config/config.exs +++ b/config/config.exs @@ -39,14 +39,16 @@ config :esbuild, env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} ] -config :tailwind, version: "3.0.24", default: [ - args: ~w( +config :tailwind, + version: "3.0.24", + default: [ + args: ~w( --config=tailwind.config.js --input=css/app.css --output=../priv/static/assets/app.css ), - cd: Path.expand("../assets", __DIR__) -] + cd: Path.expand("../assets", __DIR__) + ] # Configures Elixir's Logger config :logger, :console, diff --git a/config/prod.exs b/config/prod.exs index 21d8868..229a92f 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -9,7 +9,8 @@ import Config # manifest is generated by the `mix phx.digest` task, # which you should run after static files are built and # before starting your production server. -config :something_erlang, SomethingErlangWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json" +config :something_erlang, SomethingErlangWeb.Endpoint, + cache_static_manifest: "priv/static/cache_manifest.json" # Do not print debug messages in production config :logger, level: :info diff --git a/lib/something_erlang/application.ex b/lib/something_erlang/application.ex index c8eb705..ce50fc2 100644 --- a/lib/something_erlang/application.ex +++ b/lib/something_erlang/application.ex @@ -8,10 +8,8 @@ defmodule SomethingErlang.Application do @impl true def start(_type, _args) do children = [ - {Registry, [name: SomethingErlang.Registry.Grovers, - keys: :unique]}, - {DynamicSupervisor, [name: SomethingErlang.Supervisor.Grovers, - strategy: :one_for_one]}, + {Registry, [name: SomethingErlang.Registry.Grovers, keys: :unique]}, + {DynamicSupervisor, [name: SomethingErlang.Supervisor.Grovers, strategy: :one_for_one]}, # Start the Ecto repository SomethingErlang.Repo, # Start the Telemetry supervisor diff --git a/lib/something_erlang/awful_api/bookmarks.ex b/lib/something_erlang/awful_api/bookmarks.ex index 60036d1..d0097bd 100644 --- a/lib/something_erlang/awful_api/bookmarks.ex +++ b/lib/something_erlang/awful_api/bookmarks.ex @@ -6,6 +6,7 @@ defmodule SomethingErlang.AwfulApi.Bookmarks do def compile(page, user) do doc = Client.bookmarks_doc(page, user) html = Floki.parse_document!(doc) + for thread <- Floki.find(html, "tr.thread") do parse(thread) end @@ -17,26 +18,33 @@ defmodule SomethingErlang.AwfulApi.Bookmarks do icon: Floki.find(thread, "td.icon") |> inner_html() |> Floki.raw_html(), author: Floki.find(thread, "td.author") |> inner_html() |> Floki.text(), replies: Floki.find(thread, "td.replies") |> inner_html() |> Floki.text(), - views: Floki.find(thread, "td.views") |> inner_html() |> Floki.text(), + views: Floki.find(thread, "td.views") |> inner_html() |> Floki.text(), rating: Floki.find(thread, "td.rating") |> inner_html() |> Floki.raw_html(), lastpost: Floki.find(thread, "td.lastpost") |> inner_html() |> Floki.raw_html() } + for {"td", [{"class", class} | _attrs], children} <- Floki.find(thread, "td"), - String.starts_with?(class, "star") == false, - into: %{} do + String.starts_with?(class, "star") == false, + into: %{} do case class do <<"title", _rest::binary>> -> {:title, children |> Floki.raw_html()} + <<"icon", _rest::binary>> -> {:icon, children |> Floki.raw_html()} + <<"author", _rest::binary>> -> {:author, children |> Floki.text()} + <<"replies", _rest::binary>> -> {:replies, children |> Floki.text() |> String.to_integer()} + <<"views", _rest::binary>> -> {:views, children |> Floki.text() |> String.to_integer()} + <<"rating", _rest::binary>> -> {:rating, children |> Floki.raw_html()} + <<"lastpost", _rest::binary>> -> {:lastpost, children |> Floki.raw_html()} end diff --git a/lib/something_erlang/awful_api/client.ex b/lib/something_erlang/awful_api/client.ex index 2f464f3..e8b07e8 100644 --- a/lib/something_erlang/awful_api/client.ex +++ b/lib/something_erlang/awful_api/client.ex @@ -45,7 +45,8 @@ defmodule SomethingErlang.AwfulApi.Client do cache: true, headers: [cookie: [cookies(%{bbuserid: user.id, bbpassword: user.hash})]] ) -# |> Req.Request.append_request_steps(inspect: &IO.inspect/1) + + # |> Req.Request.append_request_steps(inspect: &IO.inspect/1) end defp cookies(args) when is_map(args) do diff --git a/lib/something_erlang/awful_api/thread.ex b/lib/something_erlang/awful_api/thread.ex index 76cd1d7..8d00102 100644 --- a/lib/something_erlang/awful_api/thread.ex +++ b/lib/something_erlang/awful_api/thread.ex @@ -8,7 +8,6 @@ defmodule SomethingErlang.AwfulApi.Thread do html = Floki.parse_document!(doc) thread = Floki.find(html, "#thread") |> Floki.filter_out("table.post.ignored") - title = Floki.find(html, "title") |> Floki.text() title = title |> String.replace(" - The Something Awful Forums", "") @@ -18,28 +17,23 @@ defmodule SomethingErlang.AwfulApi.Thread do s -> String.to_integer(s) end - posts = for post <- Floki.find(thread, "table.post") do - %{ - userinfo: post |> userinfo(), - postdate: post |> postdate(), - postbody: post |> postbody() - } - end + posts = + for post <- Floki.find(thread, "table.post") do + %{ + userinfo: post |> userinfo(), + postdate: post |> postdate(), + postbody: post |> postbody() + } + end - %{id: id, - title: title, - page: page, - page_count: page_count, - posts: posts} + %{id: id, title: title, page: page, page_count: page_count, posts: posts} end defp userinfo(post) do user = Floki.find(post, "dl.userinfo") name = user |> Floki.find("dt") |> Floki.text() regdate = user |> Floki.find("dd.registered") |> Floki.text() - title = - user |> Floki.find("dd.title") |> List.first() - |> Floki.children() |> Floki.raw_html() + title = user |> Floki.find("dd.title") |> List.first() |> Floki.children() |> Floki.raw_html() %{ name: name, @@ -49,18 +43,28 @@ defmodule SomethingErlang.AwfulApi.Thread do end defp postdate(post) do - date = - Floki.find(post, "td.postdate") - |> Floki.find("td.postdate") |> Floki.text() + date = Floki.find(post, "td.postdate") |> Floki.find("td.postdate") |> Floki.text() - [month_text, day, year, hours, minutes] = date - |> String.split(~r{[\s,:]}, trim: true) - |> Enum.drop(1) + [month_text, day, year, hours, minutes] = + date + |> String.split(~r{[\s,:]}, trim: true) + |> Enum.drop(1) - month = 1 + Enum.find_index(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], - fn m -> m == month_text end) - NaiveDateTime.new!(year |> String.to_integer(), month, day |> String.to_integer(), - hours |> String.to_integer(), minutes |> String.to_integer(), 0) + month = + 1 + + Enum.find_index( + ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], + fn m -> m == month_text end + ) + + NaiveDateTime.new!( + year |> String.to_integer(), + month, + day |> String.to_integer(), + hours |> String.to_integer(), + minutes |> String.to_integer(), + 0 + ) end defp postbody(post) do @@ -82,6 +86,7 @@ defmodule SomethingErlang.AwfulApi.Thread do defp transform(:img, attrs, _children) do {"class", class} = List.keyfind(attrs, "class", 0, {"class", ""}) + if class == "sa-smilie" do {"img", attrs, []} else @@ -92,6 +97,7 @@ defmodule SomethingErlang.AwfulApi.Thread do defp transform(:a, attrs, children) do {"href", href} = List.keyfind(attrs, "href", 0, {"href", ""}) + cond do # skip internal links String.starts_with?(href, "/") -> @@ -113,27 +119,31 @@ defmodule SomethingErlang.AwfulApi.Thread do transform_link(:ytshort, href) true -> - Logger.debug "no transform for #{href}" + Logger.debug("no transform for #{href}") {"a", [{"href", href}], children} end end defp transform_link(:mp4, href), - do: {"div", [{"class", "responsive-embed"}], - [{"video", [{"class", "img-responsive"}, {"controls", ""}], - [{"source", [{"src", href}, {"type", "video/mp4"}], []}] - }] - } + do: + {"div", [{"class", "responsive-embed"}], + [ + {"video", [{"class", "img-responsive"}, {"controls", ""}], + [{"source", [{"src", href}, {"type", "video/mp4"}], []}]} + ]} defp transform_link(:gifv, href), - do: {"div", [{"class", "responsive-embed"}], - [{"video", [{"class", "img-responsive"}, {"controls", ""}], - [{"source", [{"src", String.replace(href, ".gifv", ".webm")}, - {"type", "video/webm"}], []}, - {"source", [{"src", String.replace(href, ".gifv", ".mp4")}, - {"type", "video/mp4"}], []}] - }] - } + do: + {"div", [{"class", "responsive-embed"}], + [ + {"video", [{"class", "img-responsive"}, {"controls", ""}], + [ + {"source", [{"src", String.replace(href, ".gifv", ".webm")}, {"type", "video/webm"}], + []}, + {"source", [{"src", String.replace(href, ".gifv", ".mp4")}, {"type", "video/mp4"}], + []} + ]} + ]} defp transform_link(:ytlong, href) do String.replace(href, "/watch?v=", "/embed/") @@ -146,14 +156,15 @@ defmodule SomethingErlang.AwfulApi.Thread do end defp youtube_iframe(src), - do: {"div", [{"class", "responsive-embed"}], - [{"iframe", - [ - {"class", "youtube-player"}, - {"loading", "lazy"}, - {"allow", "fullscreen"}, - {"src", src} - ], []} - ]} - + do: + {"div", [{"class", "responsive-embed"}], + [ + {"iframe", + [ + {"class", "youtube-player"}, + {"loading", "lazy"}, + {"allow", "fullscreen"}, + {"src", src} + ], []} + ]} end diff --git a/lib/something_erlang/forums.ex b/lib/something_erlang/forums.ex index a95f017..142620b 100644 --- a/lib/something_erlang/forums.ex +++ b/lib/something_erlang/forums.ex @@ -36,7 +36,8 @@ defmodule SomethingErlang.Forums do """ def get_thread!(id), - do: %Thread{id: id, thread_id: id, title: "foo"} #Repo.get!(Thread, id) + # Repo.get!(Thread, id) + do: %Thread{id: id, thread_id: id, title: "foo"} @doc """ Creates a thread. diff --git a/lib/something_erlang/grover.ex b/lib/something_erlang/grover.ex index 7299399..3cc1fd5 100644 --- a/lib/something_erlang/grover.ex +++ b/lib/something_erlang/grover.ex @@ -5,10 +5,11 @@ defmodule SomethingErlang.Grover do require Logger def mount(user) do - {:ok, _pid} = DynamicSupervisor.start_child( - SomethingErlang.Supervisor.Grovers, - {__MODULE__, [self(), user]} - ) + {:ok, _pid} = + DynamicSupervisor.start_child( + SomethingErlang.Supervisor.Grovers, + {__MODULE__, [self(), user]} + ) end def get_thread!(thread_id, page_number) do @@ -23,18 +24,20 @@ defmodule SomethingErlang.Grover do GenServer.start_link( __MODULE__, [lv_pid, user], - name: via(lv_pid)) + name: via(lv_pid) + ) end @impl true def init([pid, user]) do %{bbuserid: userid, bbpassword: userhash} = user + initial_state = %{ lv_pid: pid, user: %{id: userid, hash: userhash} } - Logger.debug "init #{userid} #{inspect(pid)}" + Logger.debug("init #{userid} #{inspect(pid)}") Process.monitor(pid) {:ok, initial_state} end @@ -53,7 +56,8 @@ defmodule SomethingErlang.Grover do @impl true def handle_info({:DOWN, _ref, :process, _object, reason}, state) do - Logger.debug "received :DOWN from: #{inspect(state.lv_pid)} reason: #{inspect(reason)}" + Logger.debug("received :DOWN from: #{inspect(state.lv_pid)} reason: #{inspect(reason)}") + case reason do {:shutdown, _} -> {:stop, :normal, state} :killed -> {:stop, :normal, state} diff --git a/lib/something_erlang_web/controllers/page_controller.ex b/lib/something_erlang_web/controllers/page_controller.ex index 3ef4c8b..a3a1b21 100644 --- a/lib/something_erlang_web/controllers/page_controller.ex +++ b/lib/something_erlang_web/controllers/page_controller.ex @@ -7,11 +7,14 @@ defmodule SomethingErlangWeb.PageController do def to_forum_path(conn, %{"to" => redir_params} = _params) do %{"forum_path" => path} = redir_params + with [_, thread] <- Regex.run(~r{threadid=(\d+)}, path), [_, page] <- Regex.run(~r{pagenumber=(\d+)}, path) do redirect(conn, - to: Routes.thread_show_path(conn, :show, thread, page: page)) + to: Routes.thread_show_path(conn, :show, thread, page: page) + ) end + put_flash(conn, :error, "Could not resolve URL") render(conn, "index.html") end diff --git a/lib/something_erlang_web/icons.ex b/lib/something_erlang_web/icons.ex index 7c7d7e0..7928292 100644 --- a/lib/something_erlang_web/icons.ex +++ b/lib/something_erlang_web/icons.ex @@ -4,12 +4,12 @@ defmodule SomethingErlangWeb.Icons do @priv_dir Path.join(:code.priv_dir(:something_erlang), "icons") @repo_url "https://github.com/CoreyGinnivan/system-uicons.git" - System.cmd("rm", ["-rf", Path.join(@priv_dir, "system-uicons")]) + System.cmd("rm", ["-rf", Path.join(@priv_dir, "system-uicons")]) System.cmd("git", ["clone", "--depth=1", @repo_url, Path.join(@priv_dir, "system-uicons")]) source_data = File.read!(Path.join(@priv_dir, "system-uicons/src/js/data.js")) - <<"var sourceData = "::utf8 , rest::binary>> = source_data + <<"var sourceData = "::utf8, rest::binary>> = source_data # remove trailing semicolon sslice = String.slice(rest, 0..-3//1) # quote object keys @@ -18,15 +18,18 @@ defmodule SomethingErlangWeb.Icons do rm_trailing_commas = Regex.replace(~r/,\s+(}|])/, quote_keys, "\\1") icon_data = Jason.decode!(rm_trailing_commas) - icon_map = Enum.map(icon_data, fn %{"icon_path" => path} = icon -> - svg = File.read!(Path.join(@priv_dir, "system-uicons/src/images/icons/#{path}.svg")) - Map.put_new(icon, "icon_svg", svg) - |> Map.new(fn {k, v} -> {String.to_atom(k), v} end) - end) + icon_map = + Enum.map(icon_data, fn %{"icon_path" => path} = icon -> + svg = File.read!(Path.join(@priv_dir, "system-uicons/src/images/icons/#{path}.svg")) + + Map.put_new(icon, "icon_svg", svg) + |> Map.new(fn {k, v} -> {String.to_atom(k), v} end) + end) for %{icon_path: path, icon_svg: svg} <- icon_map do def unquote(String.to_atom(path))(assigns) do svg = unquote(svg) + ~H""" <%= Phoenix.HTML.raw svg %> diff --git a/lib/something_erlang_web/live/bookmarks_live/show.ex b/lib/something_erlang_web/live/bookmarks_live/show.ex index 36adb50..4f96c5d 100644 --- a/lib/something_erlang_web/live/bookmarks_live/show.ex +++ b/lib/something_erlang_web/live/bookmarks_live/show.ex @@ -15,6 +15,7 @@ defmodule SomethingErlangWeb.BookmarksLive.Show do @impl true def handle_params(%{"page" => page}, _, socket) do bookmarks = Grover.get_bookmarks!(page |> String.to_integer()) + {:noreply, socket |> assign(:page_title, "bookmarks") @@ -23,8 +24,9 @@ defmodule SomethingErlangWeb.BookmarksLive.Show do @impl true def handle_params(_, _, socket) do - {:noreply, push_redirect(socket, - to: Routes.bookmarks_show_path(socket, :show, page: 1))} + {:noreply, + push_redirect(socket, + to: Routes.bookmarks_show_path(socket, :show, page: 1) + )} end - end diff --git a/lib/something_erlang_web/live/thread_live/show.ex b/lib/something_erlang_web/live/thread_live/show.ex index 553b160..0073de0 100644 --- a/lib/something_erlang_web/live/thread_live/show.ex +++ b/lib/something_erlang_web/live/thread_live/show.ex @@ -15,6 +15,7 @@ defmodule SomethingErlangWeb.ThreadLive.Show do @impl true def handle_params(%{"id" => id, "page" => page}, _, socket) do thread = Grover.get_thread!(id, page |> String.to_integer()) + {:noreply, socket |> assign(:page_title, thread.title) @@ -23,8 +24,10 @@ defmodule SomethingErlangWeb.ThreadLive.Show do @impl true def handle_params(%{"id" => id}, _, socket) do - {:noreply, push_redirect(socket, - to: Routes.thread_show_path(socket, :show, id, page: 1))} + {:noreply, + push_redirect(socket, + to: Routes.thread_show_path(socket, :show, id, page: 1) + )} end def post(assigns) do @@ -61,10 +64,10 @@ defmodule SomethingErlangWeb.ThreadLive.Show do %{page: page_number, page_count: page_count} = assigns.thread first_page_disabled_button = if page_number == 1, do: " btn-disabled", else: "" - last_page_disabled_button = if page_number == page_count, do: " btn-disabled", else: "" + last_page_disabled_button = if page_number == page_count, do: " btn-disabled", else: "" active_page_button = " btn-active" - prev_button_target = if page_number >= 1, do: page_number - 1, else: 1 + prev_button_target = if page_number > 1, do: page_number - 1, else: 1 next_button_target = if page_number < page_count, do: page_number + 1, else: page_count buttons = [ @@ -78,16 +81,16 @@ defmodule SomethingErlangWeb.ThreadLive.Show do ~H"""