diff --git a/lib/livebook_web/helpers.ex b/lib/livebook_web/helpers.ex index 81c7881fc..00dd49885 100644 --- a/lib/livebook_web/helpers.ex +++ b/lib/livebook_web/helpers.ex @@ -86,4 +86,32 @@ defmodule LivebookWeb.Helpers do pid_str = Phoenix.LiveDashboard.Helpers.encode_pid(pid) Routes.live_dashboard_path(socket, :page, node(), "processes", info: pid_str) end + + @doc """ + Converts human-readable strings to strings which can be used + as HTML element IDs (compatible with HTML5) + + At the same time duplicate IDs are enumerated to avoid duplicates + """ + @spec names_to_html_ids(list(String.t())) :: list(String.t()) + def names_to_html_ids(names) do + names + |> Enum.map(&name_to_html_id/1) + |> Enum.map_reduce(%{}, fn html_id, counts -> + counts = Map.update(counts, html_id, 1, &(&1 + 1)) + + case counts[html_id] do + 1 -> {html_id, counts} + count -> {"#{html_id}-#{count}", counts} + end + end) + |> elem(0) + end + + defp name_to_html_id(name) do + name + |> String.trim() + |> String.downcase() + |> String.replace(~r/\s+/u, "-") + end end diff --git a/lib/livebook_web/live/session_live.ex b/lib/livebook_web/live/session_live.ex index 45d39e8ed..44f2d7b6a 100644 --- a/lib/livebook_web/live/session_live.ex +++ b/lib/livebook_web/live/session_live.ex @@ -737,7 +737,7 @@ defmodule LivebookWeb.SessionLive do data.clients_map |> Enum.map(fn {client_pid, user_id} -> {client_pid, data.users_map[user_id]} end) |> Enum.sort_by(fn {_client_pid, user} -> user.name end), - section_views: Enum.map(data.notebook.sections, §ion_to_view(&1, data)) + section_views: section_views(data.notebook.sections, data) } end @@ -768,12 +768,19 @@ defmodule LivebookWeb.SessionLive do defp evaluated?(cell, data), do: data.cell_infos[cell.id].validity_status == :evaluated - defp section_to_view(section, data) do - %{ - id: section.id, - name: section.name, - cell_views: Enum.map(section.cells, &cell_to_view(&1, data)) - } + defp section_views(sections, data) do + sections + |> Enum.map(& &1.name) + |> names_to_html_ids() + |> Enum.zip(sections) + |> Enum.map(fn {html_id, section} -> + %{ + id: section.id, + html_id: html_id, + name: section.name, + cell_views: Enum.map(section.cells, &cell_to_view(&1, data)) + } + end) end defp cell_to_view(cell, data) do diff --git a/lib/livebook_web/live/session_live/section_component.ex b/lib/livebook_web/live/session_live/section_component.ex index 097371d90..f28db4b60 100644 --- a/lib/livebook_web/live/session_live/section_component.ex +++ b/lib/livebook_web/live/session_live/section_component.ex @@ -4,7 +4,7 @@ defmodule LivebookWeb.SessionLive.SectionComponent do def render(assigns) do ~L"""