Add importing html and save it to Article
Additionally defines a wizard logic which is partially unused yet.
This commit is contained in:
@ -35,7 +35,10 @@ defmodule Outlook.Articles do
|
|||||||
** (Ecto.NoResultsError)
|
** (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 """
|
@doc """
|
||||||
Creates a article.
|
Creates a article.
|
||||||
|
|||||||
@ -2,11 +2,12 @@ defmodule Outlook.Articles.Article do
|
|||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
alias Outlook.Articles.InternalTree
|
||||||
alias Outlook.Authors.Author
|
alias Outlook.Authors.Author
|
||||||
alias Outlook.Translations.Translation
|
alias Outlook.Translations.Translation
|
||||||
|
|
||||||
schema "articles" do
|
schema "articles" do
|
||||||
field :content, :string
|
field :content, InternalTree
|
||||||
field :date, :utc_datetime
|
field :date, :utc_datetime
|
||||||
field :language, :string
|
field :language, :string
|
||||||
field :title, :string
|
field :title, :string
|
||||||
@ -20,7 +21,7 @@ defmodule Outlook.Articles.Article do
|
|||||||
@doc false
|
@doc false
|
||||||
def changeset(article, attrs) do
|
def changeset(article, attrs) do
|
||||||
article
|
article
|
||||||
|> cast(attrs, [:title, :content, :url, :language, :date])
|
|> cast(attrs, [:title, :content, :url, :language, :date, :author_id])
|
||||||
|> validate_required([:title, :content, :url, :language, :date])
|
|> validate_required([:title, :content, :url, :language, :date, :author_id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
52
lib/outlook/articles/internal_tree.ex
Normal file
52
lib/outlook/articles/internal_tree.ex
Normal file
@ -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
|
||||||
@ -37,6 +37,11 @@ defmodule Outlook.Authors do
|
|||||||
"""
|
"""
|
||||||
def get_author!(id), do: Repo.get!(Author, id)
|
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 """
|
@doc """
|
||||||
Creates a author.
|
Creates a author.
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,6 @@ defmodule Outlook.Authors.Author do
|
|||||||
def changeset(author, attrs) do
|
def changeset(author, attrs) do
|
||||||
author
|
author
|
||||||
|> cast(attrs, [:name, :description, :homepage_name, :homepage_url])
|
|> cast(attrs, [:name, :description, :homepage_name, :homepage_url])
|
||||||
|> validate_required([:name, :description, :homepage_name, :homepage_url])
|
|> validate_required([:name, :description])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -11,4 +11,10 @@ defmodule Outlook.HtmlPreparations do
|
|||||||
|> HtmlPreparation.floki_to_internal
|
|> HtmlPreparation.floki_to_internal
|
||||||
|> HtmlPreparation.set_sibling_with
|
|> HtmlPreparation.set_sibling_with
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_tree_items(content_tree) do
|
||||||
|
content_tree
|
||||||
|
|> HtmlPreparation.strip_whitespace_textnodes
|
||||||
|
|> HtmlPreparation.build_indentation_list(0)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -62,4 +62,40 @@ defmodule Outlook.HtmlPreparations.HtmlPreparation do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def set_sibling_with([ ]), do: ( [ ] )
|
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
|
end
|
||||||
|
|||||||
30
lib/outlook/internal_tree.ex
Normal file
30
lib/outlook/internal_tree.ex
Normal file
@ -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
|
||||||
52
lib/outlook/internal_tree/html.ex
Normal file
52
lib/outlook/internal_tree/html.ex
Normal file
@ -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) <>
|
||||||
|
"</#{node.name}>" <>
|
||||||
|
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
|
||||||
|
"<!--#{node.content}-->" <> to_html(rest)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_html([ %TranslationUnit{} = tunit | rest]) do
|
||||||
|
~s(<span class="tunit" uuid="#{tunit.uuid}" tu-status="#{tunit.status}">#{tunit.content}</span>) <> 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) <>
|
||||||
|
"</#{node.name}>" <>
|
||||||
|
to_html_preview(rest, target_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_html_preview([ %InternalNode{type: :text} = node | rest], target_id) do
|
||||||
|
~s(<span uuid="#{node.uuid}">#{node.content}</span>) <> to_html_preview(rest, target_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_html_preview([ %InternalNode{type: :comment} = node | rest], target_id) do
|
||||||
|
~s(<span uuid="#{node.uuid}"><!--#{node.content}--></span>) <> to_html_preview(rest, target_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_html_preview([ %TranslationUnit{} = tunit | rest], target_id) do
|
||||||
|
~s|<span class="tunit" uuid="#{tunit.uuid}" tu-status="#{tunit.status}" phx-click="select_current_tunit"
|
||||||
|
phx-value-uuid="#{tunit.uuid}" phx-target="#{target_id}">#{tunit.content}</span>| <> to_html_preview(rest, target_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_html_preview([], _target_id), do: ""
|
||||||
|
end
|
||||||
70
lib/outlook/internal_tree/internal_tree_basic.ex
Normal file
70
lib/outlook/internal_tree/internal_tree_basic.ex
Normal file
@ -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
|
||||||
19
lib/outlook/internal_tree/raw_internal_tree.ex
Normal file
19
lib/outlook/internal_tree/raw_internal_tree.ex
Normal file
@ -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
|
||||||
29
lib/outlook/internal_tree/raw_internal_tree_schema.ex
Normal file
29
lib/outlook/internal_tree/raw_internal_tree_schema.ex
Normal file
@ -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
|
||||||
4
lib/outlook/internal_tree/translation_unit.ex
Normal file
4
lib/outlook/internal_tree/translation_unit.ex
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
defmodule Outlook.InternalTree.TranslationUnit do
|
||||||
|
@derive Jason.Encoder
|
||||||
|
defstruct status: :atom, uuid: "", content: ""
|
||||||
|
end
|
||||||
@ -86,6 +86,7 @@ defmodule OutlookWeb do
|
|||||||
import Phoenix.HTML
|
import Phoenix.HTML
|
||||||
# Core UI components and translation
|
# Core UI components and translation
|
||||||
import OutlookWeb.CoreComponents
|
import OutlookWeb.CoreComponents
|
||||||
|
import OutlookWeb.HtmlTreeComponent
|
||||||
import OutlookWeb.Gettext
|
import OutlookWeb.Gettext
|
||||||
|
|
||||||
# Shortcut for generating JS commands
|
# Shortcut for generating JS commands
|
||||||
|
|||||||
57
lib/outlook_web/components/html_tree_component.ex
Normal file
57
lib/outlook_web/components/html_tree_component.ex
Normal file
@ -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"""
|
||||||
|
<div class="font-mono whitespace-nowrap">
|
||||||
|
<%= for tree_item <- @tree_items do %>
|
||||||
|
<%= case tree_item do %>
|
||||||
|
<% %{node: %{type: :element}} = item -> %>
|
||||||
|
<.tree_element node={item.node} level={item.level}></.tree_element>
|
||||||
|
<% %{node: %{type: :text}} = item -> %>
|
||||||
|
<.tree_text node={item.node} level={item.level}></.tree_text>
|
||||||
|
<% %{node: %{type: :comment}} = item -> %>
|
||||||
|
<.tree_comment node={item.node} level={item.level}></.tree_comment>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<.link phx-click={JS.push("apply_modifier", value: %{modifier: :unwrap})}>
|
||||||
|
<.button title="unwraps selected elements">Unwrap</.button>
|
||||||
|
</.link>
|
||||||
|
<.link phx-click={JS.push("partition_text", value: %{modifier: :unwrap})}>
|
||||||
|
<.button title="splits text into sentences">Partition</.button>
|
||||||
|
</.link>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
def tree_element(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div uuid={@node.uuid} phx-click={JS.push("select", value: %{uuid: @node.uuid})}>
|
||||||
|
<%= "#{String.duplicate(" ", @level)}<#{@node.name}>" %>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
def tree_text(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div uuid={@node.uuid} phx-click={JS.push("select", value: %{uuid: @node.uuid})}>
|
||||||
|
<%= "#{String.duplicate(" ", @level)}\"#{String.slice(@node.content, 0, 15)}...\"\n" %>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
def tree_comment(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div uuid={@node.uuid} phx-click={JS.push("select", value: %{uuid: @node.uuid})} title={@node.content}>
|
||||||
|
<%= "#{String.duplicate(" ", @level)}<!-- #{String.slice(@node.content, 0, 15)}...-->\n" %>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -20,8 +20,8 @@ defmodule OutlookWeb.ArticleLive.FormComponent do
|
|||||||
phx-change="validate"
|
phx-change="validate"
|
||||||
phx-submit="save"
|
phx-submit="save"
|
||||||
>
|
>
|
||||||
|
<.input field={{f, :author_id}} type="hidden" />
|
||||||
<.input field={{f, :title}} type="text" label="title" />
|
<.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, :url}} type="text" label="url" />
|
||||||
<.input field={{f, :language}} type="text" label="language" />
|
<.input field={{f, :language}} type="text" label="language" />
|
||||||
<.input field={{f, :date}} type="datetime-local" label="date" />
|
<.input field={{f, :date}} type="datetime-local" label="date" />
|
||||||
@ -54,6 +54,8 @@ defmodule OutlookWeb.ArticleLive.FormComponent do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("save", %{"article" => article_params}, socket) do
|
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)
|
save_article(socket, socket.assigns.action, article_params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<.table id="articles" rows={@articles} row_click={&JS.navigate(~p"/articles/#{&1}")}>
|
<.table id="articles" rows={@articles} row_click={&JS.navigate(~p"/articles/#{&1}")}>
|
||||||
<:col :let={article} label="Title"><%= article.title %></:col>
|
<:col :let={article} label="Title"><%= article.title %></:col>
|
||||||
<:col :let={article} label="Content"><%= article.content %></:col>
|
<%!-- <:col :let={article} label="Content"><%= article.content %></:col> --%>
|
||||||
<:col :let={article} label="Url"><%= article.url %></:col>
|
<:col :let={article} label="Url"><%= article.url %></:col>
|
||||||
<:col :let={article} label="Language"><%= article.language %></:col>
|
<:col :let={article} label="Language"><%= article.language %></:col>
|
||||||
<:col :let={article} label="Date"><%= article.date %></:col>
|
<:col :let={article} label="Date"><%= article.date %></:col>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ defmodule OutlookWeb.ArticleLive.New do
|
|||||||
|
|
||||||
import OutlookWeb.ArticleLive.NewComponents
|
import OutlookWeb.ArticleLive.NewComponents
|
||||||
|
|
||||||
|
alias OutlookWeb.ArticleLive.FormComponent
|
||||||
alias Outlook.{Articles,Authors,HtmlPreparations}
|
alias Outlook.{Articles,Authors,HtmlPreparations}
|
||||||
alias Articles.{Article,RawHtmlInput}
|
alias Articles.{Article,RawHtmlInput}
|
||||||
|
|
||||||
@ -13,7 +14,6 @@ defmodule OutlookWeb.ArticleLive.New do
|
|||||||
{:ok,
|
{:ok,
|
||||||
socket
|
socket
|
||||||
|> assign(:page_title, "New Article")
|
|> assign(:page_title, "New Article")
|
||||||
|> assign(:article, %Article{})
|
|
||||||
|> assign(:raw_html_input, %RawHtmlInput{})
|
|> assign(:raw_html_input, %RawHtmlInput{})
|
||||||
|> assign(:changeset, Articles.change_raw_html_input(%RawHtmlInput{}))
|
|> assign(:changeset, Articles.change_raw_html_input(%RawHtmlInput{}))
|
||||||
|> assign(:selected_els, [])
|
|> assign(:selected_els, [])
|
||||||
@ -23,9 +23,11 @@ defmodule OutlookWeb.ArticleLive.New do
|
|||||||
@impl true
|
@impl true
|
||||||
def handle_params(%{"author_id" => author_id}, _, socket) do
|
def handle_params(%{"author_id" => author_id}, _, socket) do
|
||||||
author = Authors.get_author!(author_id)
|
author = Authors.get_author!(author_id)
|
||||||
|
article = %Article{author_id: author.id}
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> assign(:author, author)}
|
|> assign(:author, author)
|
||||||
|
|> assign(:article, article)}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
@ -40,13 +42,24 @@ defmodule OutlookWeb.ArticleLive.New do
|
|||||||
true ->
|
true ->
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
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)}
|
|> assign(:step, :review_raw_internaltree)}
|
||||||
false ->
|
false ->
|
||||||
{:noreply, assign(socket, :changeset, changeset)}
|
{:noreply, assign(socket, :changeset, changeset)}
|
||||||
end
|
end
|
||||||
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
|
defp validate_raw_html_input(raw_html_input_params, socket) do
|
||||||
socket.assigns.raw_html_input
|
socket.assigns.raw_html_input
|
||||||
|> Articles.change_raw_html_input(raw_html_input_params)
|
|> Articles.change_raw_html_input(raw_html_input_params)
|
||||||
|
|||||||
@ -2,8 +2,19 @@
|
|||||||
New article for <%= @author.name %>
|
New article for <%= @author.name %>
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
<.import_raw_html :if={@step == :import_raw_html} changeset={@changeset}></.import_raw_html>
|
<.import_raw_html :if={@step == :import_raw_html} changeset={@changeset} />
|
||||||
<.review_raw_internaltree :if={@step == :review_raw_internaltree}></.review_raw_internaltree>
|
<.review_raw_internaltree :if={@step == :review_raw_internaltree} raw_internal_tree={@raw_internal_tree} />
|
||||||
<.review_translation_units :if={@step == :review_translation_units}></.review_translation_units>
|
<.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</.back>
|
<.back navigate={~p"/authors/#{@author}"}>Back to author</.back>
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
defmodule OutlookWeb.ArticleLive.NewComponents do
|
defmodule OutlookWeb.ArticleLive.NewComponents do
|
||||||
|
use Phoenix.Component
|
||||||
use OutlookWeb, :html
|
use OutlookWeb, :html
|
||||||
|
|
||||||
|
alias Outlook.{InternalTree,HtmlPreparations}
|
||||||
|
|
||||||
def import_raw_html(assigns) do
|
def import_raw_html(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div>
|
<div>
|
||||||
@ -13,7 +16,8 @@ defmodule OutlookWeb.ArticleLive.NewComponents do
|
|||||||
phx-change="validate_raw_html_input"
|
phx-change="validate_raw_html_input"
|
||||||
phx-submit="convert_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>
|
<:actions>
|
||||||
<.button phx-disable-with="Importing...">HTML importieren</.button>
|
<.button phx-disable-with="Importing...">HTML importieren</.button>
|
||||||
</:actions>
|
</:actions>
|
||||||
@ -25,13 +29,22 @@ defmodule OutlookWeb.ArticleLive.NewComponents do
|
|||||||
def review_raw_internaltree(assigns) do
|
def review_raw_internaltree(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div>Review Raw InternalTree</div>
|
<div>Review Raw InternalTree</div>
|
||||||
|
<div class="flex">
|
||||||
|
<div id="html-preview" class="article">
|
||||||
|
<%= InternalTree.render_html_preview(@raw_internal_tree) |> raw %>
|
||||||
|
</div>
|
||||||
|
<div id="html-tree">
|
||||||
|
<.treeview tree_items={HtmlPreparations.get_tree_items(@raw_internal_tree)} ></.treeview>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<.button phx-click="approve_raw_internaltree">Continue</.button>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
def review_translation_units(assigns) do
|
def review_translation_units(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div>Review Translation Units</div>
|
<div>Review Translation Units</div>
|
||||||
|
<.button phx-click="approve_translation_units">Continue</.button>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -2,6 +2,7 @@ defmodule OutlookWeb.ArticleLive.Show do
|
|||||||
use OutlookWeb, :live_view
|
use OutlookWeb, :live_view
|
||||||
|
|
||||||
alias Outlook.Articles
|
alias Outlook.Articles
|
||||||
|
alias Outlook.InternalTree
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(_params, _session, socket) do
|
def mount(_params, _session, socket) do
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
<.header>
|
<.header>
|
||||||
Article <%= @article.id %>
|
<a href={@article.url}><%= @article.title %></a>
|
||||||
<:subtitle>This is a article record from your database.</:subtitle>
|
<:subtitle>
|
||||||
|
<div class="flex">
|
||||||
|
<div class="p-4"><%= @article.author.name %></div>
|
||||||
|
<div class="p-4"><%= @article.date %></div>
|
||||||
|
</div>
|
||||||
|
</:subtitle>
|
||||||
<:actions>
|
<:actions>
|
||||||
<.link patch={~p"/articles/#{@article}/show/edit"} phx-click={JS.push_focus()}>
|
<.link patch={~p"/articles/#{@article}/show/edit"} phx-click={JS.push_focus()}>
|
||||||
<.button>Edit article</.button>
|
<.button>Edit article</.button>
|
||||||
@ -8,13 +13,10 @@
|
|||||||
</:actions>
|
</:actions>
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
<.list>
|
|
||||||
<:item title="Title"><%= @article.title %></:item>
|
<div class="article">
|
||||||
<:item title="Content"><%= @article.content %></:item>
|
<%= InternalTree.render_html(@article.content) |> raw %>
|
||||||
<:item title="Url"><%= @article.url %></:item>
|
</div>
|
||||||
<:item title="Language"><%= @article.language %></:item>
|
|
||||||
<:item title="Date"><%= @article.date %></:item>
|
|
||||||
</.list>
|
|
||||||
|
|
||||||
<.back navigate={~p"/articles"}>Back to articles</.back>
|
<.back navigate={~p"/articles"}>Back to articles</.back>
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ defmodule OutlookWeb.AuthorLive.Show do
|
|||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> assign(:page_title, page_title(socket.assigns.live_action))
|
|> assign(:page_title, page_title(socket.assigns.live_action))
|
||||||
|> assign(:author, Authors.get_author!(id))}
|
|> assign(:author, Authors.get_author_with_articles!(id))}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp page_title(:show), do: "Show Author"
|
defp page_title(:show), do: "Show Author"
|
||||||
|
|||||||
@ -18,6 +18,26 @@
|
|||||||
<:item title="Homepage url"><%= @author.homepage_url %></:item>
|
<:item title="Homepage url"><%= @author.homepage_url %></:item>
|
||||||
</.list>
|
</.list>
|
||||||
|
|
||||||
|
<.table id="articles" rows={@author.articles} row_click={&JS.navigate(~p"/articles/#{&1}")}>
|
||||||
|
<:col :let={article} label="Title"><%= article.title %></:col>
|
||||||
|
<%!-- <:col :let={article} label="Content"><%= article.content %></:col> --%>
|
||||||
|
<:col :let={article} label="Url"><%= article.url %></:col>
|
||||||
|
<:col :let={article} label="Language"><%= article.language %></:col>
|
||||||
|
<:col :let={article} label="Date"><%= article.date %></:col>
|
||||||
|
<:action :let={article}>
|
||||||
|
<div class="sr-only">
|
||||||
|
<.link navigate={~p"/articles/#{article}"}>Show</.link>
|
||||||
|
</div>
|
||||||
|
<.link patch={~p"/articles/#{article}/edit"}>Edit</.link>
|
||||||
|
</:action>
|
||||||
|
<:action :let={article}>
|
||||||
|
<.link phx-click={JS.push("delete", value: %{id: article.id})} data-confirm="Are you sure?">
|
||||||
|
Delete
|
||||||
|
</.link>
|
||||||
|
</:action>
|
||||||
|
</.table>
|
||||||
|
|
||||||
|
|
||||||
<.back navigate={~p"/authors"}>Back to authors</.back>
|
<.back navigate={~p"/authors"}>Back to authors</.back>
|
||||||
|
|
||||||
<.modal :if={@live_action == :edit} id="author-modal" show on_cancel={JS.patch(~p"/authors/#{@author}")}>
|
<.modal :if={@live_action == :edit} id="author-modal" show on_cancel={JS.patch(~p"/authors/#{@author}")}>
|
||||||
|
|||||||
Reference in New Issue
Block a user