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