mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-10-11 14:06:20 +08:00
Export with title or file name (#870)
* Export file with title or file name * Export with title or file name * Add Session.file_name_for_download/1 * Compute the name without calling the server Co-authored-by: Jonatan Kłosko <jonatanklosko@gmail.com>
This commit is contained in:
parent
be1fce326c
commit
358bdb3267
5 changed files with 67 additions and 14 deletions
|
@ -157,6 +157,27 @@ defmodule Livebook.Session do
|
|||
GenServer.call(pid, :get_notebook, @timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Computes the file name for download.
|
||||
|
||||
Note that the name doesn't have any extension.
|
||||
|
||||
If the notebook has an associated file, the same name is used,
|
||||
otherwise it is computed from the notebook title.
|
||||
"""
|
||||
@spec file_name_for_download(t()) :: String.t()
|
||||
def file_name_for_download(session)
|
||||
|
||||
def file_name_for_download(%{file: nil} = session) do
|
||||
notebook_name_to_file_name(session.notebook_name)
|
||||
end
|
||||
|
||||
def file_name_for_download(session) do
|
||||
session.file
|
||||
|> FileSystem.File.name()
|
||||
|> Path.rootname()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetches assets matching the given hash.
|
||||
|
||||
|
@ -406,7 +427,8 @@ defmodule Livebook.Session do
|
|||
If there's a file set and the notebook changed since the last save,
|
||||
it will be persisted to said file.
|
||||
|
||||
Note that notebooks are automatically persisted every @autosave_interval milliseconds.
|
||||
Note that notebooks are automatically persisted every @autosave_interval
|
||||
milliseconds.
|
||||
"""
|
||||
@spec save(pid()) :: :ok
|
||||
def save(pid) do
|
||||
|
@ -1237,11 +1259,7 @@ defmodule Livebook.Session do
|
|||
end
|
||||
|
||||
defp default_notebook_path(state) do
|
||||
title_str =
|
||||
state.data.notebook.name
|
||||
|> String.downcase()
|
||||
|> String.replace(~r/\s+/, "_")
|
||||
|> String.replace(~r/[^\w]/, "")
|
||||
title_str = notebook_name_to_file_name(state.data.notebook.name)
|
||||
|
||||
# We want a random, but deterministic part, so we
|
||||
# use a few trailing characters from the session id,
|
||||
|
@ -1257,6 +1275,17 @@ defmodule Livebook.Session do
|
|||
"#{date_str}/#{time_str}_#{title_str}_#{random_str}.livemd"
|
||||
end
|
||||
|
||||
defp notebook_name_to_file_name(notebook_name) do
|
||||
notebook_name
|
||||
|> String.downcase()
|
||||
|> String.replace(~r/\s+/, "_")
|
||||
|> String.replace(~r/[^\w]/, "")
|
||||
|> case do
|
||||
"" -> "untitled_notebook"
|
||||
name -> name
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_save_finished(state, result, file, default?) do
|
||||
state =
|
||||
if default? do
|
||||
|
|
|
@ -18,34 +18,35 @@ defmodule LivebookWeb.SessionController do
|
|||
case Sessions.fetch_session(id) do
|
||||
{:ok, session} ->
|
||||
notebook = Session.get_notebook(session.pid)
|
||||
file_name = Session.file_name_for_download(session)
|
||||
|
||||
send_notebook_source(conn, notebook, format)
|
||||
send_notebook_source(conn, notebook, file_name, format)
|
||||
|
||||
:error ->
|
||||
send_resp(conn, 404, "Not found")
|
||||
end
|
||||
end
|
||||
|
||||
defp send_notebook_source(conn, notebook, "livemd") do
|
||||
defp send_notebook_source(conn, notebook, file_name, "livemd" = format) do
|
||||
opts = [include_outputs: conn.params["include_outputs"] == "true"]
|
||||
source = Livebook.LiveMarkdown.Export.notebook_to_markdown(notebook, opts)
|
||||
|
||||
send_download(conn, {:binary, source},
|
||||
filename: "notebook.livemd",
|
||||
filename: file_name <> "." <> format,
|
||||
content_type: "text/plain"
|
||||
)
|
||||
end
|
||||
|
||||
defp send_notebook_source(conn, notebook, "exs") do
|
||||
defp send_notebook_source(conn, notebook, file_name, "exs" = format) do
|
||||
source = Livebook.Notebook.Export.Elixir.notebook_to_elixir(notebook)
|
||||
|
||||
send_download(conn, {:binary, source},
|
||||
filename: "notebook.exs",
|
||||
filename: file_name <> "." <> format,
|
||||
content_type: "text/plain"
|
||||
)
|
||||
end
|
||||
|
||||
defp send_notebook_source(conn, _notebook, _format) do
|
||||
defp send_notebook_source(conn, _notebook, _file_name, _format) do
|
||||
send_resp(conn, 400, "Invalid format, supported formats: livemd, exs")
|
||||
end
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
defmodule LivebookWeb.SessionLive.ExportElixirComponent do
|
||||
use LivebookWeb, :live_component
|
||||
|
||||
alias Livebook.Session
|
||||
|
||||
@impl true
|
||||
def update(assigns, socket) do
|
||||
socket = assign(socket, assigns)
|
||||
|
@ -28,7 +30,7 @@ defmodule LivebookWeb.SessionLive.ExportElixirComponent do
|
|||
<div class="flex flex-col space-y-1">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-gray-700 font-semibold">
|
||||
.exs
|
||||
<%= Session.file_name_for_download(@session) <> ".exs" %>
|
||||
</span>
|
||||
<div class="flex justify-end space-x-2">
|
||||
<span class="tooltip left" data-tooltip="Copy source">
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
defmodule LivebookWeb.SessionLive.ExportLiveMarkdownComponent do
|
||||
use LivebookWeb, :live_component
|
||||
|
||||
alias Livebook.Session
|
||||
|
||||
@impl true
|
||||
def update(assigns, socket) do
|
||||
socket = assign(socket, assigns)
|
||||
|
@ -35,7 +37,7 @@ defmodule LivebookWeb.SessionLive.ExportLiveMarkdownComponent do
|
|||
<div class="flex flex-col space-y-1">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-gray-700 font-semibold">
|
||||
.livemd
|
||||
<%= Session.file_name_for_download(@session) <> ".livemd" %>
|
||||
</span>
|
||||
<div class="flex justify-end space-x-2">
|
||||
<span class="tooltip left" data-tooltip="Copy source">
|
||||
|
|
|
@ -9,6 +9,25 @@ defmodule Livebook.SessionTest do
|
|||
%{session: session}
|
||||
end
|
||||
|
||||
describe "file_name_for_download/1" do
|
||||
@tag :tmp_dir
|
||||
test "uses associated file name if one is attached", %{tmp_dir: tmp_dir} do
|
||||
tmp_dir = FileSystem.File.local(tmp_dir <> "/")
|
||||
file = FileSystem.File.resolve(tmp_dir, "my_notebook.livemd")
|
||||
session = start_session(file: file)
|
||||
|
||||
assert Session.file_name_for_download(session) == "my_notebook"
|
||||
end
|
||||
|
||||
test "defaults to notebook name", %{session: session} do
|
||||
Session.set_notebook_name(session.pid, "Cat's guide to life!")
|
||||
# Get the updated struct
|
||||
session = Session.get_by_pid(session.pid)
|
||||
|
||||
assert Session.file_name_for_download(session) == "cats_guide_to_life"
|
||||
end
|
||||
end
|
||||
|
||||
describe "set_notebook_attributes/2" do
|
||||
test "sends an attributes update to subscribers", %{session: session} do
|
||||
Phoenix.PubSub.subscribe(Livebook.PubSub, "sessions:#{session.id}")
|
||||
|
|
Loading…
Add table
Reference in a new issue