diff --git a/lib/livebook_web/live/home_live.ex b/lib/livebook_web/live/home_live.ex index 8da48ab7b..7772553b6 100644 --- a/lib/livebook_web/live/home_live.ex +++ b/lib/livebook_web/live/home_live.ex @@ -139,7 +139,8 @@ defmodule LivebookWeb.HomeLive do id: "import", modal_class: "w-full max-w-xl", return_to: Routes.home_path(@socket, :page), - tab: @tab %> + tab: @tab, + import_opts: @import_opts %> <% end %> """ end @@ -220,8 +221,23 @@ defmodule LivebookWeb.HomeLive do {:noreply, assign(socket, session: session)} end - def handle_params(%{"tab" => tab}, _url, socket) do - {:noreply, assign(socket, tab: tab)} + def handle_params(%{"tab" => tab} = params, _url, %{assigns: %{live_action: :import}} = socket) do + import_opts = [url: params["url"]] + {:noreply, assign(socket, tab: tab, import_opts: import_opts)} + end + + def handle_params(%{"url" => url}, _url, %{assigns: %{live_action: :public_import}} = socket) do + url + |> Livebook.ContentLoader.rewrite_url() + |> Livebook.ContentLoader.fetch_content() + |> case do + {:ok, content} -> + socket = import_content(socket, content, origin: {:url, url}) + {:noreply, socket} + + {:error, _message} -> + {:noreply, push_patch(socket, to: Routes.home_path(socket, :import, "url", url: url))} + end end def handle_params(_params, _url, socket), do: {:noreply, socket} @@ -331,10 +347,8 @@ defmodule LivebookWeb.HomeLive do end def handle_info({:import_content, content, session_opts}, socket) do - {notebook, messages} = Livebook.LiveMarkdown.Import.notebook_from_markdown(content) - socket = put_import_flash_messages(socket, messages) - session_opts = Keyword.merge(session_opts, notebook: notebook) - {:noreply, create_session(socket, session_opts)} + socket = import_content(socket, content, session_opts) + {:noreply, socket} end def handle_info( @@ -391,4 +405,11 @@ defmodule LivebookWeb.HomeLive do time_words = created_at |> DateTime.to_naive() |> Livebook.Utils.Time.time_ago_in_words() time_words <> " ago" end + + defp import_content(socket, content, session_opts) do + {notebook, messages} = Livebook.LiveMarkdown.Import.notebook_from_markdown(content) + socket = put_import_flash_messages(socket, messages) + session_opts = Keyword.merge(session_opts, notebook: notebook) + create_session(socket, session_opts) + end end diff --git a/lib/livebook_web/live/home_live/import_component.ex b/lib/livebook_web/live/home_live/import_component.ex index 1f41e4783..10778a28a 100644 --- a/lib/livebook_web/live/home_live/import_component.ex +++ b/lib/livebook_web/live/home_live/import_component.ex @@ -27,7 +27,7 @@ defmodule LivebookWeb.HomeLive.ImportComponent do
- <%= live_component component_for_tab(@tab), id: "import-#{@tab}" %> + <%= live_component component_for_tab(@tab), [{:id, "import-#{@tab}"} | @import_opts] %>
""" diff --git a/lib/livebook_web/live/home_live/import_url_component.ex b/lib/livebook_web/live/home_live/import_url_component.ex index 73e6f94ea..bf39e38c8 100644 --- a/lib/livebook_web/live/home_live/import_url_component.ex +++ b/lib/livebook_web/live/home_live/import_url_component.ex @@ -8,6 +8,15 @@ defmodule LivebookWeb.HomeLive.ImportUrlComponent do {:ok, assign(socket, url: "", error_message: nil)} end + @impl true + def update(assigns, socket) do + if url = assigns[:url] do + {:ok, do_import(socket, url)} + else + {:ok, socket} + end + end + @impl true def render(assigns) do ~H""" @@ -45,16 +54,20 @@ defmodule LivebookWeb.HomeLive.ImportUrlComponent do end def handle_event("import", %{"data" => %{"url" => url}}, socket) do + {:noreply, do_import(socket, url)} + end + + defp do_import(socket, url) do url |> ContentLoader.rewrite_url() |> ContentLoader.fetch_content() |> case do {:ok, content} -> send(self(), {:import_content, content, [origin: {:url, url}]}) - {:noreply, socket} + socket {:error, message} -> - {:noreply, assign(socket, error_message: Utils.upcase_first(message))} + assign(socket, url: url, error_message: Utils.upcase_first(message)) end end end diff --git a/lib/livebook_web/router.ex b/lib/livebook_web/router.ex index 5f5c6cf00..110f7fd4d 100644 --- a/lib/livebook_web/router.ex +++ b/lib/livebook_web/router.ex @@ -52,6 +52,13 @@ defmodule LivebookWeb.Router do home_app: {"Livebook", :livebook} end + # Public URLs that people may be directed to + scope "/", LivebookWeb do + pipe_through [:browser, :auth] + + live "/import", HomeLive, :public_import + end + scope "/", LivebookWeb do pipe_through :browser diff --git a/test/livebook_web/live/home_live_test.exs b/test/livebook_web/live/home_live_test.exs index 20703a4ce..f1e636056 100644 --- a/test/livebook_web/live/home_live_test.exs +++ b/test/livebook_web/live/home_live_test.exs @@ -208,6 +208,44 @@ defmodule LivebookWeb.HomeLiveTest do end end + describe "public import endpoint" do + test "imports notebook from the given url and redirects to the new session", %{conn: conn} do + bypass = Bypass.open() + + Bypass.expect_once(bypass, "GET", "/notebook", fn conn -> + conn + |> Plug.Conn.put_resp_content_type("text/plain") + |> Plug.Conn.resp(200, "# My notebook") + end) + + notebook_url = "http://localhost:#{bypass.port}/notebook" + + assert {:error, {:live_redirect, %{to: to}}} = + live(conn, "/import?url=#{URI.encode_www_form(notebook_url)}") + + {:ok, view, _} = live(conn, to) + assert render(view) =~ "My notebook" + end + + test "redirects to the import form on error", %{conn: conn} do + bypass = Bypass.open() + + Bypass.expect(bypass, "GET", "/notebook", fn conn -> + Plug.Conn.resp(conn, 500, "Error") + end) + + notebook_url = "http://localhost:#{bypass.port}/notebook" + + assert {:error, {:live_redirect, %{to: to}}} = + live(conn, "/import?url=#{URI.encode_www_form(notebook_url)}") + + assert to == "/home/import/url?url=#{URI.encode_www_form(notebook_url)}" + + {:ok, view, _} = live(conn, to) + assert render(view) =~ notebook_url + end + end + # Helpers defp test_notebook_path(name) do