mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-09-04 12:04:20 +08:00
Start new notebooks with a focused code cell (#527)
* Start new notebooks with a focused code cell * Update changelog
This commit is contained in:
parent
806101aa25
commit
2b1cb57a15
7 changed files with 71 additions and 26 deletions
|
@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
### Changed
|
||||
|
||||
- Improved intellisense to handle structs and sigils ([#513](https://github.com/livebook-dev/livebook/pull/513))
|
||||
- Create new notebooks with an already focused code cell ([#527](https://github.com/livebook-dev/livebook/pull/527))
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
selectElementContent,
|
||||
smoothlyScrollToElement,
|
||||
} from "../lib/utils";
|
||||
import { getAttributeOrDefault } from "../lib/attribute";
|
||||
import KeyBuffer from "./key_buffer";
|
||||
import { globalPubSub } from "../lib/pub_sub";
|
||||
import monaco from "../cell/live_editor/monaco";
|
||||
|
@ -18,6 +19,11 @@ import monaco from "../cell/live_editor/monaco";
|
|||
* to communicate between this global hook and cells and for
|
||||
* that we use a simple local pubsub that the hooks subscribe to.
|
||||
*
|
||||
* Configuration:
|
||||
*
|
||||
* * `data-autofocus-cell-id` - id of the cell that gets initial
|
||||
* focus once the notebook is loaded
|
||||
*
|
||||
* ## Shortcuts
|
||||
*
|
||||
* This hook registers session shortcut handlers,
|
||||
|
@ -51,6 +57,7 @@ import monaco from "../cell/live_editor/monaco";
|
|||
*/
|
||||
const Session = {
|
||||
mounted() {
|
||||
this.props = getProps(this);
|
||||
this.state = {
|
||||
focusedCellId: null,
|
||||
focusedSectionId: null,
|
||||
|
@ -117,7 +124,7 @@ const Session = {
|
|||
window.addEventListener(
|
||||
"phx:page-loading-stop",
|
||||
() => {
|
||||
focusCellFromUrl(this);
|
||||
initializeFocus(this);
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
|
@ -205,6 +212,16 @@ const Session = {
|
|||
},
|
||||
};
|
||||
|
||||
function getProps(hook) {
|
||||
return {
|
||||
autofocusCellId: getAttributeOrDefault(
|
||||
hook.el,
|
||||
"data-autofocus-cell-id",
|
||||
null
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Data of a specific LV client.
|
||||
*
|
||||
|
@ -481,24 +498,30 @@ function handleCellIndicatorsClick(hook, event) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Focuses cell based on the given URL.
|
||||
* Focuses cell or any other element based on the current
|
||||
* URL and hook attributes.
|
||||
*/
|
||||
function focusCellFromUrl(hook) {
|
||||
function initializeFocus(hook) {
|
||||
const hash = window.location.hash;
|
||||
|
||||
if (hash.startsWith("#cell-")) {
|
||||
const cellId = hash.replace(/^#cell-/, "");
|
||||
if (getCellById(cellId)) {
|
||||
setFocusedCell(hook, cellId);
|
||||
}
|
||||
} else {
|
||||
// Explicitly scroll to the target element
|
||||
// after the loading finishes
|
||||
const htmlId = hash.replace(/^#/, "");
|
||||
const element = document.getElementById(htmlId);
|
||||
if (element) {
|
||||
element.scrollIntoView();
|
||||
if (hash) {
|
||||
if (hash.startsWith("#cell-")) {
|
||||
const cellId = hash.replace(/^#cell-/, "");
|
||||
if (getCellById(cellId)) {
|
||||
setFocusedCell(hook, cellId);
|
||||
}
|
||||
} else {
|
||||
// Explicitly scroll to the target element
|
||||
// after the loading finishes
|
||||
const htmlId = hash.replace(/^#/, "");
|
||||
const element = document.getElementById(htmlId);
|
||||
if (element) {
|
||||
element.scrollIntoView();
|
||||
}
|
||||
}
|
||||
} else if (hook.props.autofocusCellId) {
|
||||
setFocusedCell(hook, hook.props.autofocusCellId, false);
|
||||
setInsertMode(hook, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -398,11 +398,11 @@ defmodule Livebook.Session do
|
|||
end
|
||||
|
||||
defp init_data(opts) do
|
||||
notebook = opts[:notebook]
|
||||
notebook = Keyword.get_lazy(opts, :notebook, &default_notebook/0)
|
||||
file = opts[:file]
|
||||
origin = opts[:origin]
|
||||
|
||||
data = if(notebook, do: Data.new(notebook), else: Data.new())
|
||||
data = Data.new(notebook)
|
||||
data = %{data | origin: origin}
|
||||
|
||||
if file do
|
||||
|
@ -418,6 +418,10 @@ defmodule Livebook.Session do
|
|||
end
|
||||
end
|
||||
|
||||
defp default_notebook() do
|
||||
%{Notebook.new() | sections: [%{Section.new() | cells: [Cell.new(:elixir)]}]}
|
||||
end
|
||||
|
||||
defp schedule_autosave(state) do
|
||||
if interval_s = state.data.notebook.autosave_interval_s do
|
||||
ref = Process.send_after(self(), :autosave, interval_s * 1000)
|
||||
|
|
|
@ -38,7 +38,8 @@ defmodule LivebookWeb.SessionLive do
|
|||
session_pid: session_pid,
|
||||
current_user: current_user,
|
||||
self: self(),
|
||||
data_view: data_to_view(data)
|
||||
data_view: data_to_view(data),
|
||||
autofocus_cell_id: autofocus_cell_id(data.notebook)
|
||||
)
|
||||
|> assign_private(data: data)
|
||||
|> allow_upload(:cell_image,
|
||||
|
@ -74,7 +75,8 @@ defmodule LivebookWeb.SessionLive do
|
|||
<div class="flex flex-grow h-full"
|
||||
id="session"
|
||||
data-element="session"
|
||||
phx-hook="Session">
|
||||
phx-hook="Session"
|
||||
data-autofocus-cell-id={@autofocus_cell_id}>
|
||||
<SidebarHelpers.sidebar>
|
||||
<SidebarHelpers.logo_item socket={@socket} />
|
||||
<SidebarHelpers.button_item
|
||||
|
@ -952,7 +954,11 @@ defmodule LivebookWeb.SessionLive do
|
|||
end
|
||||
end
|
||||
|
||||
defp after_operation(socket, _prev_socket, {:delete_section, _client_pid, section_id}) do
|
||||
defp after_operation(
|
||||
socket,
|
||||
_prev_socket,
|
||||
{:delete_section, _client_pid, section_id, _delete_cells}
|
||||
) do
|
||||
push_event(socket, "section_deleted", %{section_id: section_id})
|
||||
end
|
||||
|
||||
|
@ -1092,6 +1098,9 @@ defmodule LivebookWeb.SessionLive do
|
|||
|
||||
defp process_intellisense_response(response, _request), do: response
|
||||
|
||||
defp autofocus_cell_id(%Notebook{sections: [%{cells: [%{id: id, source: ""}]}]}), do: id
|
||||
defp autofocus_cell_id(_notebook), do: nil
|
||||
|
||||
# Builds view-specific structure of data by cherry-picking
|
||||
# only the relevant attributes.
|
||||
# We then use `@data_view` in the templates and consequently
|
||||
|
|
|
@ -307,7 +307,7 @@ defmodule Livebook.SessionTest do
|
|||
Session.save(session_id)
|
||||
|
||||
assert_receive {:operation, {:mark_as_not_dirty, _}}
|
||||
assert FileSystem.File.read(file) == {:ok, "# My notebook\n"}
|
||||
assert {:ok, "# My notebook\n" <> _rest} = FileSystem.File.read(file)
|
||||
end
|
||||
|
||||
@tag :tmp_dir
|
||||
|
@ -325,7 +325,7 @@ defmodule Livebook.SessionTest do
|
|||
Session.save(session_id)
|
||||
|
||||
assert_receive {:operation, {:mark_as_not_dirty, _}}
|
||||
assert FileSystem.File.read(file) == {:ok, "# My notebook\n"}
|
||||
assert {:ok, "# My notebook\n" <> _rest} = FileSystem.File.read(file)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -347,7 +347,7 @@ defmodule Livebook.SessionTest do
|
|||
Session.close(session_id)
|
||||
|
||||
assert_receive :session_closed
|
||||
assert FileSystem.File.read(file) == {:ok, "# My notebook\n"}
|
||||
assert {:ok, "# My notebook\n" <> _rest} = FileSystem.File.read(file)
|
||||
end
|
||||
|
||||
test "clears session temporary directory", %{session_id: session_id} do
|
||||
|
|
|
@ -63,6 +63,12 @@ defmodule LivebookWeb.SessionControllerTest do
|
|||
|
||||
assert conn.resp_body == """
|
||||
# Untitled notebook
|
||||
|
||||
## Section
|
||||
|
||||
```elixir
|
||||
|
||||
```
|
||||
"""
|
||||
|
||||
SessionSupervisor.close_session(session_id)
|
||||
|
@ -124,6 +130,8 @@ defmodule LivebookWeb.SessionControllerTest do
|
|||
|
||||
assert conn.resp_body == """
|
||||
# Title: Untitled notebook
|
||||
|
||||
# ── Section ──
|
||||
"""
|
||||
|
||||
SessionSupervisor.close_session(session_id)
|
||||
|
|
|
@ -8,7 +8,7 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
alias Livebook.Users.User
|
||||
|
||||
setup do
|
||||
{:ok, session_id} = SessionSupervisor.create_session()
|
||||
{:ok, session_id} = SessionSupervisor.create_session(notebook: Livebook.Notebook.new())
|
||||
%{session_id: session_id}
|
||||
end
|
||||
|
||||
|
@ -54,7 +54,7 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
|
||||
cell_id = insert_text_cell(session_id, section_id, :markdown)
|
||||
|
||||
assert render(view) =~ cell_id
|
||||
assert render(view) =~ "cell-" <> cell_id
|
||||
end
|
||||
|
||||
test "un-renders a deleted cell", %{conn: conn, session_id: session_id} do
|
||||
|
@ -66,7 +66,7 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
Session.delete_cell(session_id, cell_id)
|
||||
wait_for_session_update(session_id)
|
||||
|
||||
refute render(view) =~ cell_id
|
||||
refute render(view) =~ "cell-" <> cell_id
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue