From b7bd9195b69bfb465739ddc2d21de083a285154c Mon Sep 17 00:00:00 2001 From: Thelonius Kort Date: Thu, 29 Dec 2022 16:43:52 +0100 Subject: [PATCH] Add importing html and save it to Article Additionally defines a wizard logic which is partially unused yet. --- lib/outlook/articles.ex | 5 +- lib/outlook/articles/article.ex | 7 +- lib/outlook/articles/internal_tree.ex | 52 ++++++++++++++ lib/outlook/authors.ex | 5 ++ lib/outlook/authors/author.ex | 2 +- lib/outlook/html_preparations.ex | 6 ++ .../html_preparations/html_preparation.ex | 36 ++++++++++ lib/outlook/internal_tree.ex | 30 ++++++++ lib/outlook/internal_tree/html.ex | 52 ++++++++++++++ .../internal_tree/internal_tree_basic.ex | 70 +++++++++++++++++++ .../internal_tree/raw_internal_tree.ex | 19 +++++ .../internal_tree/raw_internal_tree_schema.ex | 29 ++++++++ lib/outlook/internal_tree/translation_unit.ex | 4 ++ lib/outlook_web.ex | 1 + .../components/html_tree_component.ex | 57 +++++++++++++++ .../live/article_live/form_component.ex | 4 +- .../live/article_live/index.html.heex | 2 +- lib/outlook_web/live/article_live/new.ex | 19 ++++- .../live/article_live/new.html.heex | 17 ++++- .../live/article_live/new_components.ex | 17 ++++- lib/outlook_web/live/article_live/show.ex | 1 + .../live/article_live/show.html.heex | 20 +++--- lib/outlook_web/live/author_live/show.ex | 2 +- .../live/author_live/show.html.heex | 20 ++++++ 24 files changed, 452 insertions(+), 25 deletions(-) create mode 100644 lib/outlook/articles/internal_tree.ex create mode 100644 lib/outlook/internal_tree.ex create mode 100644 lib/outlook/internal_tree/html.ex create mode 100644 lib/outlook/internal_tree/internal_tree_basic.ex create mode 100644 lib/outlook/internal_tree/raw_internal_tree.ex create mode 100644 lib/outlook/internal_tree/raw_internal_tree_schema.ex create mode 100644 lib/outlook/internal_tree/translation_unit.ex create mode 100644 lib/outlook_web/components/html_tree_component.ex diff --git a/lib/outlook/articles.ex b/lib/outlook/articles.ex index 1d13105..999f493 100644 --- a/lib/outlook/articles.ex +++ b/lib/outlook/articles.ex @@ -35,7 +35,10 @@ defmodule Outlook.Articles do ** (Ecto.NoResultsError) """ - def get_article!(id), do: Repo.get!(Article, id) + def get_article!(id) do + Repo.get!(Article, id) + |> Repo.preload([:author]) + end @doc """ Creates a article. diff --git a/lib/outlook/articles/article.ex b/lib/outlook/articles/article.ex index 1c4a25c..86e7f9c 100644 --- a/lib/outlook/articles/article.ex +++ b/lib/outlook/articles/article.ex @@ -2,11 +2,12 @@ defmodule Outlook.Articles.Article do use Ecto.Schema import Ecto.Changeset + alias Outlook.Articles.InternalTree alias Outlook.Authors.Author alias Outlook.Translations.Translation schema "articles" do - field :content, :string + field :content, InternalTree field :date, :utc_datetime field :language, :string field :title, :string @@ -20,7 +21,7 @@ defmodule Outlook.Articles.Article do @doc false def changeset(article, attrs) do article - |> cast(attrs, [:title, :content, :url, :language, :date]) - |> validate_required([:title, :content, :url, :language, :date]) + |> cast(attrs, [:title, :content, :url, :language, :date, :author_id]) + |> validate_required([:title, :content, :url, :language, :date, :author_id]) end end diff --git a/lib/outlook/articles/internal_tree.ex b/lib/outlook/articles/internal_tree.ex new file mode 100644 index 0000000..fedfce0 --- /dev/null +++ b/lib/outlook/articles/internal_tree.ex @@ -0,0 +1,52 @@ +defmodule Outlook.Articles.InternalTree do + use Ecto.Type + + alias Outlook.InternalTree.InternalNode + alias Outlook.InternalTree.TranslationUnit + + def type, do: :string + + def cast(tree) when is_list(tree) do + {:ok, tree} + end + + def cast(_), do: :error + + def load(tree) when is_binary(tree) do + {:ok, Jason.decode!(tree, keys: :atoms!) |> from_json} + end + + def dump(tree) when is_list(tree), do: {:ok, Jason.encode!(tree)} + def dump(_), do: :error + + + defp from_json([%{status: _} = node | rest]) do + [ %TranslationUnit{ + status: String.to_atom(node.status), + uuid: node.uuid, + content: node.content + } | from_json(rest) ] + end + + defp from_json([%{type: "element"} = node | rest]) do + [ %InternalNode{ + name: node.name, + attributes: node.attributes, + type: String.to_atom(node.type), + uuid: node.uuid, + content: from_json(node.content) + } | from_json(rest) ] + end + + defp from_json([%{type: _} = node | rest]) do + [ %InternalNode{ + name: node.name, + attributes: node.attributes, + type: String.to_atom(node.type), + uuid: node.uuid, + content: node.content + } | from_json(rest) ] + end + + defp from_json([]), do: [] +end diff --git a/lib/outlook/authors.ex b/lib/outlook/authors.ex index 180b7b0..6edb2e1 100644 --- a/lib/outlook/authors.ex +++ b/lib/outlook/authors.ex @@ -37,6 +37,11 @@ defmodule Outlook.Authors do """ def get_author!(id), do: Repo.get!(Author, id) + def get_author_with_articles!(id) do + Repo.get!(Author, id) + |> Repo.preload([:articles]) + end + @doc """ Creates a author. diff --git a/lib/outlook/authors/author.ex b/lib/outlook/authors/author.ex index 626f8d8..704f5bb 100644 --- a/lib/outlook/authors/author.ex +++ b/lib/outlook/authors/author.ex @@ -18,6 +18,6 @@ defmodule Outlook.Authors.Author do def changeset(author, attrs) do author |> cast(attrs, [:name, :description, :homepage_name, :homepage_url]) - |> validate_required([:name, :description, :homepage_name, :homepage_url]) + |> validate_required([:name, :description]) end end diff --git a/lib/outlook/html_preparations.ex b/lib/outlook/html_preparations.ex index cb8c4b5..864a1ba 100644 --- a/lib/outlook/html_preparations.ex +++ b/lib/outlook/html_preparations.ex @@ -11,4 +11,10 @@ defmodule Outlook.HtmlPreparations do |> HtmlPreparation.floki_to_internal |> HtmlPreparation.set_sibling_with end + + def get_tree_items(content_tree) do + content_tree + |> HtmlPreparation.strip_whitespace_textnodes + |> HtmlPreparation.build_indentation_list(0) + end end diff --git a/lib/outlook/html_preparations/html_preparation.ex b/lib/outlook/html_preparations/html_preparation.ex index 1deee75..d8013f4 100644 --- a/lib/outlook/html_preparations/html_preparation.ex +++ b/lib/outlook/html_preparations/html_preparation.ex @@ -62,4 +62,40 @@ defmodule Outlook.HtmlPreparations.HtmlPreparation do end def set_sibling_with([ ]), do: ( [ ] ) + + def strip_whitespace_textnodes [ %{type: :text} = node | rest] do + if Regex.match?(~r/^\s*$/, node.content) do + strip_whitespace_textnodes(rest) + else + [ node | strip_whitespace_textnodes(rest)] + end + end + + + def strip_whitespace_textnodes [ %{type: :element} = node | rest] do + [ %InternalNode{ node | content: strip_whitespace_textnodes(node.content) } + | strip_whitespace_textnodes(rest) ] + end + + def strip_whitespace_textnodes [ node | rest] do + [ node | strip_whitespace_textnodes(rest) ] + end + + def strip_whitespace_textnodes([]), do: [] + + + def build_indentation_list [ %{type: :element} = node | rest], level do + [ %{node: Map.replace(node, :content, []), level: level} + | [ build_indentation_list(node.content, level + 1) + | build_indentation_list(rest, level) + ] + ] |> List.flatten + end + + def build_indentation_list [ node | rest ], level do + [ %{node: node, level: level} + | build_indentation_list( rest, level ) ] + end + + def build_indentation_list([ ], _), do: [] end diff --git a/lib/outlook/internal_tree.ex b/lib/outlook/internal_tree.ex new file mode 100644 index 0000000..ac15018 --- /dev/null +++ b/lib/outlook/internal_tree.ex @@ -0,0 +1,30 @@ +defmodule Outlook.InternalTree do + + alias Outlook.InternalTree.{Html,Modifiers,Basic} + alias Outlook.HtmlPreparations.HtmlPreparation + + def render_html(tree) do + tree + |> HtmlPreparation.strip_whitespace_textnodes() + |> Html.to_html() + end + + def render_html_preview(tree) do + tree + |> partition_text + |> Html.to_html_preview("1") + end + + require Logger + def apply_modifier(tree, modifier, uuids, opts \\ %{}) do + # Logger.info modifier + Modifiers.traverse_tree(tree, modifier, uuids, opts) + end + + def partition_text(tree) do + # validate_sibling_collocation(tree) + tree + |> Basic.set_split_markers() + |> Basic.partition_textnodes() + end +end diff --git a/lib/outlook/internal_tree/html.ex b/lib/outlook/internal_tree/html.ex new file mode 100644 index 0000000..ab33aaa --- /dev/null +++ b/lib/outlook/internal_tree/html.ex @@ -0,0 +1,52 @@ +defmodule Outlook.InternalTree.Html do + + alias Outlook.InternalTree.InternalNode + alias Outlook.InternalTree.TranslationUnit + + def to_html([ %InternalNode{type: :element} = node | rest]) do + attr_string = Map.put(node.attributes, :uuid, node.uuid) + |> Enum.map_join(" ", fn {k,v} -> "#{k}=\"#{v}\"" end) + "<#{node.name} #{attr_string}>" <> + to_html(node.content) <> + "" <> + to_html(rest) + end + + def to_html([ %InternalNode{type: :text} = node | rest]) do + node.content <> to_html(rest) + end + + def to_html([ %InternalNode{type: :comment} = node | rest]) do + "" <> to_html(rest) + end + + def to_html([ %TranslationUnit{} = tunit | rest]) do + ~s(#{tunit.content}) <> to_html(rest) + end + + def to_html([]), do: "" + + def to_html_preview([ %InternalNode{type: :element} = node | rest], target_id) do + attr_string = Map.put(node.attributes, :uuid, node.uuid) + |> Enum.map_join(" ", fn {k,v} -> "#{k}=\"#{v}\"" end) + "<#{node.name} #{attr_string}>" <> + to_html_preview(node.content, target_id) <> + "" <> + to_html_preview(rest, target_id) + end + + def to_html_preview([ %InternalNode{type: :text} = node | rest], target_id) do + ~s(#{node.content}) <> to_html_preview(rest, target_id) + end + + def to_html_preview([ %InternalNode{type: :comment} = node | rest], target_id) do + ~s() <> to_html_preview(rest, target_id) + end + + def to_html_preview([ %TranslationUnit{} = tunit | rest], target_id) do + ~s|#{tunit.content}| <> to_html_preview(rest, target_id) + end + + def to_html_preview([], _target_id), do: "" +end diff --git a/lib/outlook/internal_tree/internal_tree_basic.ex b/lib/outlook/internal_tree/internal_tree_basic.ex new file mode 100644 index 0000000..c844fde --- /dev/null +++ b/lib/outlook/internal_tree/internal_tree_basic.ex @@ -0,0 +1,70 @@ +defmodule Outlook.InternalTree.Basic do + + alias Ecto.UUID + alias Outlook.InternalTree.InternalNode + alias Outlook.InternalTree.TranslationUnit + alias Outlook.InternalTree.Html + + @splitmarker "@@translationunit@@" + + def set_split_markers([ %InternalNode{type: :text} = textnode | rest ]) do + [ %InternalNode{textnode | + content: String.replace(textnode.content, ~r|([.?!]["'”]?\s*)|u, "\\1#{@splitmarker}") + } | set_split_markers(rest) ] + end + + def set_split_markers([ %InternalNode{type: :element} = node | rest ]) do + [ %InternalNode{node | content: set_split_markers(node.content)} + | set_split_markers(rest) ] + end + + def set_split_markers([ node | rest ]) do + [ node | set_split_markers(rest) ] + end + + def set_split_markers([]), do: [] + + def partition_textnodes([ %InternalNode{type: :element} = node | rest ]) do + [ %InternalNode{node | content: case get_sibling_collocation(node.content) do + :block -> partition_textnodes(node.content) + :inline -> inline_to_translation_units(node.content) + _ -> node # rare case of only whitespace textnode(s) - does this ever happen? + end + } | partition_textnodes(rest) ] + end + + def partition_textnodes([ node | rest ]) do + [ node | partition_textnodes(rest) ] + end + + def partition_textnodes([]), do: [] + + + defp inline_to_translation_units(contents) do + contents + # |> Html.strip_attributes # to be implemented + |> Html.to_html() + |> String.split(@splitmarker, trim: true) + |> Enum.map(fn sentence -> + %TranslationUnit{ + content: sentence, + status: :untranslated, + uuid: UUID.generate() + } + end + ) + end + + defp contains_elements?(content) do + + end + + @doc "Returns just either :block or :inline. Assumes that it doesn't contain both." + def get_sibling_collocation(content) do + content + |> Enum.map(fn node -> node.sibling_with end) + |> Enum.uniq() + |> List.delete(:both) + |> List.first + end +end diff --git a/lib/outlook/internal_tree/raw_internal_tree.ex b/lib/outlook/internal_tree/raw_internal_tree.ex new file mode 100644 index 0000000..c693856 --- /dev/null +++ b/lib/outlook/internal_tree/raw_internal_tree.ex @@ -0,0 +1,19 @@ +defmodule Outlook.Articles.RawInternalTree do + use Ecto.Type + + def type, do: :string + + def cast([] = tree) do + {:ok, tree} + end + + def cast(_), do: :error + + def load(tree) when is_binary(tree) do + {:ok, Jason.decode!(tree, keys: :atoms!)} + end + + def dump([] = tree ), do: {:ok, Jason.encode!(tree)} + def dump(_), do: :error + +end diff --git a/lib/outlook/internal_tree/raw_internal_tree_schema.ex b/lib/outlook/internal_tree/raw_internal_tree_schema.ex new file mode 100644 index 0000000..8ae9d4b --- /dev/null +++ b/lib/outlook/internal_tree/raw_internal_tree_schema.ex @@ -0,0 +1,29 @@ +defmodule Outlook.Articles.RawInternalTreeSchema do + use Ecto.Schema + import Ecto.Changeset + + alias Outlook.Articles.RawInternalTree + + embedded_schema do + field :tree, RawInternalTree + end + + @doc false + def changeset(raw_tree, attrs) do + raw_tree + |> check_sibling_collocation(:tree) + end + + def check_sibling_collocation(changeset, field) when is_atom(field) do + validate_change(changeset, field, fn field, value -> + case value do + true -> + [] + + false -> + [{field, "html should be useful"}] + end + end) + end + +end diff --git a/lib/outlook/internal_tree/translation_unit.ex b/lib/outlook/internal_tree/translation_unit.ex new file mode 100644 index 0000000..e85e564 --- /dev/null +++ b/lib/outlook/internal_tree/translation_unit.ex @@ -0,0 +1,4 @@ +defmodule Outlook.InternalTree.TranslationUnit do + @derive Jason.Encoder + defstruct status: :atom, uuid: "", content: "" +end diff --git a/lib/outlook_web.ex b/lib/outlook_web.ex index 35a8d97..1c7a24a 100644 --- a/lib/outlook_web.ex +++ b/lib/outlook_web.ex @@ -86,6 +86,7 @@ defmodule OutlookWeb do import Phoenix.HTML # Core UI components and translation import OutlookWeb.CoreComponents + import OutlookWeb.HtmlTreeComponent import OutlookWeb.Gettext # Shortcut for generating JS commands diff --git a/lib/outlook_web/components/html_tree_component.ex b/lib/outlook_web/components/html_tree_component.ex new file mode 100644 index 0000000..79574fc --- /dev/null +++ b/lib/outlook_web/components/html_tree_component.ex @@ -0,0 +1,57 @@ +defmodule OutlookWeb.HtmlTreeComponent do + + use Phoenix.Component + # use OutlookWeb, :html + import OutlookWeb.CoreComponents + + alias Phoenix.LiveView.JS + + attr :tree_items, :list, required: true + + def treeview(assigns) do + ~H""" +
+ <%= for tree_item <- @tree_items do %> + <%= case tree_item do %> + <% %{node: %{type: :element}} = item -> %> + <.tree_element node={item.node} level={item.level}> + <% %{node: %{type: :text}} = item -> %> + <.tree_text node={item.node} level={item.level}> + <% %{node: %{type: :comment}} = item -> %> + <.tree_comment node={item.node} level={item.level}> + <% end %> + <% end %> +
+ <.link phx-click={JS.push("apply_modifier", value: %{modifier: :unwrap})}> + <.button title="unwraps selected elements">Unwrap + + <.link phx-click={JS.push("partition_text", value: %{modifier: :unwrap})}> + <.button title="splits text into sentences">Partition + + """ + end + + def tree_element(assigns) do + ~H""" +
+ <%= "#{String.duplicate("  ", @level)}<#{@node.name}>" %> +
+ """ + end + + def tree_text(assigns) do + ~H""" +
+ <%= "#{String.duplicate("  ", @level)}\"#{String.slice(@node.content, 0, 15)}...\"\n" %> +
+ """ + end + + def tree_comment(assigns) do + ~H""" +
+ <%= "#{String.duplicate("  ", @level)}\n" %> +
+ """ + end +end diff --git a/lib/outlook_web/live/article_live/form_component.ex b/lib/outlook_web/live/article_live/form_component.ex index 6801087..f6aff2c 100644 --- a/lib/outlook_web/live/article_live/form_component.ex +++ b/lib/outlook_web/live/article_live/form_component.ex @@ -20,8 +20,8 @@ defmodule OutlookWeb.ArticleLive.FormComponent do phx-change="validate" phx-submit="save" > + <.input field={{f, :author_id}} type="hidden" /> <.input field={{f, :title}} type="text" label="title" /> - <.input field={{f, :content}} type="text" label="content" /> <.input field={{f, :url}} type="text" label="url" /> <.input field={{f, :language}} type="text" label="language" /> <.input field={{f, :date}} type="datetime-local" label="date" /> @@ -54,6 +54,8 @@ defmodule OutlookWeb.ArticleLive.FormComponent do end def handle_event("save", %{"article" => article_params}, socket) do + article_params = article_params + |> Map.put("content", socket.assigns.internal_tree) save_article(socket, socket.assigns.action, article_params) end diff --git a/lib/outlook_web/live/article_live/index.html.heex b/lib/outlook_web/live/article_live/index.html.heex index 84704e2..e3b4d21 100644 --- a/lib/outlook_web/live/article_live/index.html.heex +++ b/lib/outlook_web/live/article_live/index.html.heex @@ -6,7 +6,7 @@ <.table id="articles" rows={@articles} row_click={&JS.navigate(~p"/articles/#{&1}")}> <:col :let={article} label="Title"><%= article.title %> - <:col :let={article} label="Content"><%= article.content %> + <%!-- <:col :let={article} label="Content"><%= article.content %> --%> <:col :let={article} label="Url"><%= article.url %> <:col :let={article} label="Language"><%= article.language %> <:col :let={article} label="Date"><%= article.date %> diff --git a/lib/outlook_web/live/article_live/new.ex b/lib/outlook_web/live/article_live/new.ex index 5d29f1b..f8ed083 100644 --- a/lib/outlook_web/live/article_live/new.ex +++ b/lib/outlook_web/live/article_live/new.ex @@ -3,6 +3,7 @@ defmodule OutlookWeb.ArticleLive.New do import OutlookWeb.ArticleLive.NewComponents + alias OutlookWeb.ArticleLive.FormComponent alias Outlook.{Articles,Authors,HtmlPreparations} alias Articles.{Article,RawHtmlInput} @@ -13,7 +14,6 @@ defmodule OutlookWeb.ArticleLive.New do {:ok, socket |> assign(:page_title, "New Article") - |> assign(:article, %Article{}) |> assign(:raw_html_input, %RawHtmlInput{}) |> assign(:changeset, Articles.change_raw_html_input(%RawHtmlInput{})) |> assign(:selected_els, []) @@ -23,9 +23,11 @@ defmodule OutlookWeb.ArticleLive.New do @impl true def handle_params(%{"author_id" => author_id}, _, socket) do author = Authors.get_author!(author_id) + article = %Article{author_id: author.id} {:noreply, socket - |> assign(:author, author)} + |> assign(:author, author) + |> assign(:article, article)} end @impl true @@ -40,13 +42,24 @@ defmodule OutlookWeb.ArticleLive.New do true -> {:noreply, socket - |> assign(:raw_internal_tree, HtmlPreparations.convert_raw_html_input(raw_html_input_params["content"])) + |> assign(:raw_internal_tree, + HtmlPreparations.convert_raw_html_input(raw_html_input_params["content"])) |> assign(:step, :review_raw_internaltree)} false -> {:noreply, assign(socket, :changeset, changeset)} end end + @impl true + def handle_event("approve_raw_internaltree", _, socket) do + {:noreply, socket |> assign(:step, :review_translation_units)} + end + + @impl true + def handle_event("approve_translation_units", _, socket) do + {:noreply, socket |> assign(:step, :final_form)} + end + defp validate_raw_html_input(raw_html_input_params, socket) do socket.assigns.raw_html_input |> Articles.change_raw_html_input(raw_html_input_params) diff --git a/lib/outlook_web/live/article_live/new.html.heex b/lib/outlook_web/live/article_live/new.html.heex index f3ba423..96d9310 100644 --- a/lib/outlook_web/live/article_live/new.html.heex +++ b/lib/outlook_web/live/article_live/new.html.heex @@ -2,8 +2,19 @@ New article for <%= @author.name %> -<.import_raw_html :if={@step == :import_raw_html} changeset={@changeset}> -<.review_raw_internaltree :if={@step == :review_raw_internaltree}> -<.review_translation_units :if={@step == :review_translation_units}> +<.import_raw_html :if={@step == :import_raw_html} changeset={@changeset} /> +<.review_raw_internaltree :if={@step == :review_raw_internaltree} raw_internal_tree={@raw_internal_tree} /> +<.review_translation_units :if={@step == :review_translation_units} /> +<.live_component + :if={@step == :final_form} + module={OutlookWeb.ArticleLive.FormComponent} + id={:new} + title="New Article" + action={:new} + article={@article} + author={@author} + internal_tree={@raw_internal_tree} + navigate={~p"/authors/#{@author}"} +/> <.back navigate={~p"/authors/#{@author}"}>Back to author diff --git a/lib/outlook_web/live/article_live/new_components.ex b/lib/outlook_web/live/article_live/new_components.ex index 231fde6..c67a420 100644 --- a/lib/outlook_web/live/article_live/new_components.ex +++ b/lib/outlook_web/live/article_live/new_components.ex @@ -1,6 +1,9 @@ defmodule OutlookWeb.ArticleLive.NewComponents do + use Phoenix.Component use OutlookWeb, :html + alias Outlook.{InternalTree,HtmlPreparations} + def import_raw_html(assigns) do ~H"""
@@ -13,7 +16,8 @@ defmodule OutlookWeb.ArticleLive.NewComponents do phx-change="validate_raw_html_input" phx-submit="convert_raw_html_input" > - <.input field={{f, :content}} type="textarea" label="text to import" phx-debounce="500" /> + <.input field={{f, :content}} type="textarea" label="text to import" phx-debounce="500" + class="h-96" /> <:actions> <.button phx-disable-with="Importing...">HTML importieren @@ -25,13 +29,22 @@ defmodule OutlookWeb.ArticleLive.NewComponents do def review_raw_internaltree(assigns) do ~H"""
Review Raw InternalTree
- +
+
+ <%= InternalTree.render_html_preview(@raw_internal_tree) |> raw %> +
+
+ <.treeview tree_items={HtmlPreparations.get_tree_items(@raw_internal_tree)} > +
+
+ <.button phx-click="approve_raw_internaltree">Continue """ end def review_translation_units(assigns) do ~H"""
Review Translation Units
+ <.button phx-click="approve_translation_units">Continue """ end end diff --git a/lib/outlook_web/live/article_live/show.ex b/lib/outlook_web/live/article_live/show.ex index cf86edc..8c42719 100644 --- a/lib/outlook_web/live/article_live/show.ex +++ b/lib/outlook_web/live/article_live/show.ex @@ -2,6 +2,7 @@ defmodule OutlookWeb.ArticleLive.Show do use OutlookWeb, :live_view alias Outlook.Articles + alias Outlook.InternalTree @impl true def mount(_params, _session, socket) do diff --git a/lib/outlook_web/live/article_live/show.html.heex b/lib/outlook_web/live/article_live/show.html.heex index 29321ec..e3e0d07 100644 --- a/lib/outlook_web/live/article_live/show.html.heex +++ b/lib/outlook_web/live/article_live/show.html.heex @@ -1,6 +1,11 @@ <.header> - Article <%= @article.id %> - <:subtitle>This is a article record from your database. + <%= @article.title %> + <:subtitle> +
+
<%= @article.author.name %>
+
<%= @article.date %>
+
+ <:actions> <.link patch={~p"/articles/#{@article}/show/edit"} phx-click={JS.push_focus()}> <.button>Edit article @@ -8,13 +13,10 @@ -<.list> - <:item title="Title"><%= @article.title %> - <:item title="Content"><%= @article.content %> - <:item title="Url"><%= @article.url %> - <:item title="Language"><%= @article.language %> - <:item title="Date"><%= @article.date %> - + +
+ <%= InternalTree.render_html(@article.content) |> raw %> +
<.back navigate={~p"/articles"}>Back to articles diff --git a/lib/outlook_web/live/author_live/show.ex b/lib/outlook_web/live/author_live/show.ex index 3e0777e..73e59a7 100644 --- a/lib/outlook_web/live/author_live/show.ex +++ b/lib/outlook_web/live/author_live/show.ex @@ -13,7 +13,7 @@ defmodule OutlookWeb.AuthorLive.Show do {:noreply, socket |> assign(:page_title, page_title(socket.assigns.live_action)) - |> assign(:author, Authors.get_author!(id))} + |> assign(:author, Authors.get_author_with_articles!(id))} end defp page_title(:show), do: "Show Author" diff --git a/lib/outlook_web/live/author_live/show.html.heex b/lib/outlook_web/live/author_live/show.html.heex index 9c70c74..abca2c1 100644 --- a/lib/outlook_web/live/author_live/show.html.heex +++ b/lib/outlook_web/live/author_live/show.html.heex @@ -18,6 +18,26 @@ <:item title="Homepage url"><%= @author.homepage_url %> +<.table id="articles" rows={@author.articles} row_click={&JS.navigate(~p"/articles/#{&1}")}> + <:col :let={article} label="Title"><%= article.title %> + <%!-- <:col :let={article} label="Content"><%= article.content %> --%> + <:col :let={article} label="Url"><%= article.url %> + <:col :let={article} label="Language"><%= article.language %> + <:col :let={article} label="Date"><%= article.date %> + <:action :let={article}> +
+ <.link navigate={~p"/articles/#{article}"}>Show +
+ <.link patch={~p"/articles/#{article}/edit"}>Edit + + <:action :let={article}> + <.link phx-click={JS.push("delete", value: %{id: article.id})} data-confirm="Are you sure?"> + Delete + + + + + <.back navigate={~p"/authors"}>Back to authors <.modal :if={@live_action == :edit} id="author-modal" show on_cancel={JS.patch(~p"/authors/#{@author}")}>