Compare commits
34 Commits
3fe4a331ac
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 2111cfa1d1 | |||
| c8d1639afa | |||
| 43e11178db | |||
| 4828a68bcd | |||
| 2b25c13095 | |||
| 357bcae450 | |||
| 698da7f8b7 | |||
| d53eceb7a0 | |||
| 888e7575f0 | |||
| 8a0e2f22c1 | |||
| 7990b74bf0 | |||
| 4a8f1c3c80 | |||
| 7e085f5202 | |||
| 3b8b1b19f0 | |||
| 5ac1a6c8e7 | |||
| 3a2af2adb4 | |||
| b92503d643 | |||
| 2eba3bc500 | |||
| 43f3ea193f | |||
| 61253f301a | |||
| 5d43e61223 | |||
| 724d161f50 | |||
| 1fb9a40f2c | |||
| 459c8e6a57 | |||
| 21b97bec6e | |||
| 5d9238a65a | |||
| e20f8e33ee | |||
| 4e6c516cb6 | |||
| bacb61252f | |||
| 8a513b1452 | |||
| 895860baa6 | |||
| 5319785855 | |||
| b20bbb232c | |||
| cbea9450e4 |
@ -1,9 +1,8 @@
|
|||||||
alias Outlook.HtmlPreparations
|
alias Outlook.HtmlPreparations
|
||||||
alias Outlook.HtmlPreparations.HtmlPreparation
|
alias Outlook.HtmlPreparations.HtmlPreparation
|
||||||
alias Outlook.InternalTree.{Html,InternalNode,TranslationUnit}
|
alias Outlook.InternalTree.{Html,InternalNode,TranslationUnit,TunitModifications}
|
||||||
alias Outlook.InternalTree
|
alias Outlook.InternalTree
|
||||||
alias Outlook.Articles
|
alias Outlook.Articles
|
||||||
alias Outlook.Artikel
|
|
||||||
alias Outlook.Accounts
|
alias Outlook.Accounts
|
||||||
alias Outlook.Articles.Article
|
alias Outlook.Articles.Article
|
||||||
alias Outlook.Authors
|
alias Outlook.Authors
|
||||||
@ -12,6 +11,8 @@ alias Outlook.Translations
|
|||||||
alias Outlook.Translations.Translation
|
alias Outlook.Translations.Translation
|
||||||
alias Outlook.Translators.{Deepl,DeeplAccount}
|
alias Outlook.Translators.{Deepl,DeeplAccount}
|
||||||
alias Outlook.Translators
|
alias Outlook.Translators
|
||||||
|
alias Outlook.Public
|
||||||
|
alias Outlook.Public.{Artikel,Autor}
|
||||||
alias Outlook.Repo
|
alias Outlook.Repo
|
||||||
import Ecto.Query, warn: false
|
import Ecto.Query, warn: false
|
||||||
html = """
|
html = """
|
||||||
|
|||||||
@ -7,12 +7,43 @@
|
|||||||
max-width: 25rem;
|
max-width: 25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.article .tunit {
|
.article span.tunit {
|
||||||
@apply hover:bg-gray-300;
|
@apply hover:bg-gray-300;
|
||||||
|
padding: 5px 0;
|
||||||
|
margin: -5px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .article .tunit {
|
.dark .article span.tunit {
|
||||||
@apply hover:bg-gray-700;
|
@apply hover:bg-gray-700;
|
||||||
|
padding: 5px 0;
|
||||||
|
margin: -5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article span.tunit[current="yes"], .article span.tunit[selected="yes"] {
|
||||||
|
@apply bg-amber-300 text-stone-700 hover:bg-amber-200 hover:text-red-900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .article span.tunit[current="yes"] {
|
||||||
|
@apply bg-amber-500/70 text-white hover:bg-amber-500/70 hover:text-red-900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article.show_status span.tunit a {
|
||||||
|
@apply text-inherit;
|
||||||
|
}
|
||||||
|
.article.show_status span.tunit[status="untranslated"] {
|
||||||
|
@apply text-red-900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article.show_status span.tunit[status="passable"] {
|
||||||
|
@apply text-amber-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .article.show_status span.tunit[status="untranslated"] {
|
||||||
|
@apply text-red-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .article.show_status span.tunit[status="passable"] {
|
||||||
|
@apply text-amber-100;
|
||||||
}
|
}
|
||||||
|
|
||||||
.article a.hide-link {
|
.article a.hide-link {
|
||||||
|
|||||||
@ -23,11 +23,14 @@ import {LiveSocket} from "phoenix_live_view"
|
|||||||
import topbar from "../vendor/topbar"
|
import topbar from "../vendor/topbar"
|
||||||
|
|
||||||
import {DarkModeHook} from './dark-mode-widget'
|
import {DarkModeHook} from './dark-mode-widget'
|
||||||
|
import {TranslationFormHook} from "./translation-form"
|
||||||
|
|
||||||
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
|
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
|
||||||
let liveSocket = new LiveSocket("/live", Socket, {
|
let liveSocket = new LiveSocket("/live", Socket, {
|
||||||
params: {_csrf_token: csrfToken},
|
params: {_csrf_token: csrfToken},
|
||||||
hooks: {dark_mode_widget: DarkModeHook},
|
hooks: {translation_form: TranslationFormHook,
|
||||||
|
// tunit_editor: TunitEditorHook,
|
||||||
|
dark_mode_widget: DarkModeHook},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Show progress bar on live navigation and form submits
|
// Show progress bar on live navigation and form submits
|
||||||
|
|||||||
54
assets/js/translation-form.js
Normal file
54
assets/js/translation-form.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
let TranslationFormHook = {
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.el.addEventListener("keyup", this.keyupHandler.bind(this))
|
||||||
|
this.title_input = this.el.querySelector("#translation-form_title")
|
||||||
|
this.tunit_editor = this.el.querySelector("#tunit-editor-content")
|
||||||
|
this.save_edit_button = this.el.querySelector("#save-edit-button")
|
||||||
|
this.save_publish_button = this.el.querySelector("#save-publish-button")
|
||||||
|
let article_preview_links = document.querySelectorAll(".article-preview a")
|
||||||
|
article_preview_links.forEach(el => el.addEventListener('click', e => e.preventDefault()))
|
||||||
|
},
|
||||||
|
keyupHandler(e) {
|
||||||
|
var push_event = true
|
||||||
|
var preaction = () => { }
|
||||||
|
var postaction = () => { }
|
||||||
|
var payload = {}
|
||||||
|
if (e.altKey){
|
||||||
|
if (e.ctrlKey){
|
||||||
|
if (e.key == "s"){
|
||||||
|
this.save_edit_button.click()
|
||||||
|
} else if (e.key == "p"){
|
||||||
|
this.save_publish_button.click()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (e.key == "ArrowDown" || e.key == "n"){
|
||||||
|
preaction = () => { this.title_input.focus() }
|
||||||
|
postaction = () => { this.tunit_editor.focus() }
|
||||||
|
var handler = "select_next_tunit"
|
||||||
|
} else if (e.key == "ArrowUp" || e.key == "v"){
|
||||||
|
preaction = () => { this.title_input.focus() }
|
||||||
|
postaction = () => { this.tunit_editor.focus() }
|
||||||
|
var handler = "select_previous_tunit"
|
||||||
|
} else if (e.key == "u") {
|
||||||
|
var handler = "tunit_status"
|
||||||
|
payload = {status: "untranslated"}
|
||||||
|
} else if (e.key == "p") {
|
||||||
|
var handler = "tunit_status"
|
||||||
|
payload = {status: "passable"}
|
||||||
|
} else if (e.key == "o") {
|
||||||
|
var handler = "tunit_status"
|
||||||
|
payload = {status: "done"}
|
||||||
|
} else {
|
||||||
|
push_event = false
|
||||||
|
}
|
||||||
|
if (push_event) {
|
||||||
|
preaction.call()
|
||||||
|
this.pushEventTo(this.el, handler, payload, postaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export {TranslationFormHook}
|
||||||
@ -12,6 +12,7 @@ defmodule Outlook.Articles.Article do
|
|||||||
field :language, :string, default: "EN"
|
field :language, :string, default: "EN"
|
||||||
field :title, :string
|
field :title, :string
|
||||||
field :url, :string
|
field :url, :string
|
||||||
|
field :remarks, :string
|
||||||
belongs_to :author, Author
|
belongs_to :author, Author
|
||||||
has_many :translations, Translation, on_delete: :delete_all
|
has_many :translations, Translation, on_delete: :delete_all
|
||||||
|
|
||||||
@ -21,7 +22,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, :author_id])
|
|> cast(attrs, [:title, :content, :url, :language, :date, :author_id, :remarks])
|
||||||
|> validate_required([:title, :content, :url, :language, :date, :author_id])
|
|> validate_required([:title, :content, :url, :language, :date, :author_id])
|
||||||
|> foreign_key_constraint(:author_id)
|
|> foreign_key_constraint(:author_id)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -39,7 +39,7 @@ defmodule Outlook.Authors do
|
|||||||
|
|
||||||
def get_author_with_articles!(id) do
|
def get_author_with_articles!(id) do
|
||||||
Repo.get!(Author, id)
|
Repo.get!(Author, id)
|
||||||
|> Repo.preload([:articles])
|
|> Repo.preload([articles: :translations])
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|||||||
@ -9,7 +9,7 @@ defmodule Outlook.Authors.Author do
|
|||||||
field :homepage_name, :string
|
field :homepage_name, :string
|
||||||
field :homepage_url, :string
|
field :homepage_url, :string
|
||||||
field :name, :string
|
field :name, :string
|
||||||
has_many :articles, Article
|
has_many :articles, Article, on_delete: :delete_all
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
defmodule Outlook.InternalTree do
|
defmodule Outlook.InternalTree do
|
||||||
|
|
||||||
alias Outlook.InternalTree.{Html,Modifiers,RawInternalBasic,InternalTree,Translation}
|
alias Outlook.InternalTree.{Html,Modifiers,RawInternalBasic,InternalTree,Translation,TunitModifications}
|
||||||
alias Outlook.HtmlPreparations.HtmlPreparation
|
alias Outlook.HtmlPreparations.HtmlPreparation
|
||||||
alias Outlook.{Hyphenation, Translations}
|
alias Outlook.{Hyphenation, Translations}
|
||||||
|
|
||||||
@ -10,11 +10,6 @@ defmodule Outlook.InternalTree do
|
|||||||
|> Html.to_html()
|
|> Html.to_html()
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_html_preview(tree, target \\ "1") do
|
|
||||||
tree
|
|
||||||
|> Html.to_html_preview(target)
|
|
||||||
end
|
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
def apply_modifier(tree, modifier, nids, opts \\ %{}) do
|
def apply_modifier(tree, modifier, nids, opts \\ %{}) do
|
||||||
# Logger.info modifier
|
# Logger.info modifier
|
||||||
@ -31,9 +26,12 @@ defmodule Outlook.InternalTree do
|
|||||||
def add_phx_click_event(tree, opts) do
|
def add_phx_click_event(tree, opts) do
|
||||||
phx_opts = %{
|
phx_opts = %{
|
||||||
"phx-click": Keyword.get(opts, :click),
|
"phx-click": Keyword.get(opts, :click),
|
||||||
"phx-target": Keyword.get(opts, :target) |> to_string,
|
|
||||||
"phx-value-nid": fn n -> n.nid end
|
"phx-value-nid": fn n -> n.nid end
|
||||||
}
|
}
|
||||||
|
phx_opts = case Keyword.has_key?(opts, :target) do
|
||||||
|
true -> Map.put(phx_opts, "phx-target", Keyword.get(opts, :target) |> to_string)
|
||||||
|
false -> phx_opts
|
||||||
|
end
|
||||||
options = Map.put(%{}, Keyword.get(opts, :nodes, :elements), phx_opts)
|
options = Map.put(%{}, Keyword.get(opts, :nodes, :elements), phx_opts)
|
||||||
garnish(tree, options)
|
garnish(tree, options)
|
||||||
end
|
end
|
||||||
@ -58,6 +56,7 @@ defmodule Outlook.InternalTree do
|
|||||||
|
|
||||||
def render_public_content(tree, translation, language) do
|
def render_public_content(tree, translation, language) do
|
||||||
Translation.render_translation(tree, translation)
|
Translation.render_translation(tree, translation)
|
||||||
|
|> garnish(%{})
|
||||||
|> Html.render_doc()
|
|> Html.render_doc()
|
||||||
|> Hyphenation.hyphenate(language)
|
|> Hyphenation.hyphenate(language)
|
||||||
end
|
end
|
||||||
@ -74,6 +73,13 @@ defmodule Outlook.InternalTree do
|
|||||||
|
|
||||||
def get_tunit_ids(tree) do
|
def get_tunit_ids(tree) do
|
||||||
InternalTree.collect_tunit_ids(tree)
|
InternalTree.collect_tunit_ids(tree)
|
||||||
# |> List.flatten()
|
end
|
||||||
|
|
||||||
|
def modify_tunits(tree, modifier, tu_ids) do
|
||||||
|
TunitModifications.apply_modifier(tree, modifier, tu_ids)
|
||||||
|
end
|
||||||
|
|
||||||
|
def tunit_modifiers() do
|
||||||
|
TunitModifications.modifiers()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -51,30 +51,6 @@ defmodule Outlook.InternalTree.Html do
|
|||||||
|
|
||||||
def to_html([]), do: ""
|
def to_html([]), do: ""
|
||||||
|
|
||||||
def to_html_preview([ %InternalNode{type: :element} = node | rest], target_id) do
|
|
||||||
attr_string = Map.put(node.attributes, :nid, node.nid)
|
|
||||||
|> 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 nid="#{node.nid}">#{node.content}</span>) <> to_html_preview(rest, target_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_html_preview([ %InternalNode{type: :comment} = node | rest], target_id) do
|
|
||||||
~s(<span nid="#{node.nid}"><!--#{node.content}--></span>) <> to_html_preview(rest, target_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_html_preview([ %TranslationUnit{} = tunit | rest], target_id) do
|
|
||||||
~s|<span class="tunit" nid="#{tunit.nid}" tu-status="#{tunit.status}" phx-click="select_current_tunit"
|
|
||||||
phx-value-nid="#{tunit.nid}" phx-target="#{target_id}">#{tunit.content}</span>| <> to_html_preview(rest, target_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_html_preview([], _target_id), do: ""
|
|
||||||
|
|
||||||
|
|
||||||
def render_doc(tree) do
|
def render_doc(tree) do
|
||||||
OutlookWeb.HtmlDocComponent.render_doc(%{tree: tree})
|
OutlookWeb.HtmlDocComponent.render_doc(%{tree: tree})
|
||||||
|
|||||||
@ -10,7 +10,7 @@ defmodule Outlook.InternalTree.RawInternalBasic do
|
|||||||
|
|
||||||
@splitmarker "@@translationunit@@"
|
@splitmarker "@@translationunit@@"
|
||||||
@nonperiodmarker "@@nonperiod@@"
|
@nonperiodmarker "@@nonperiod@@"
|
||||||
@void_elements ~w(img br hr)
|
@void_elements ~w(img br hr iframe)
|
||||||
|
|
||||||
def set_split_markers([ %InternalNode{type: :text} = textnode | rest ]) do
|
def set_split_markers([ %InternalNode{type: :text} = textnode | rest ]) do
|
||||||
[ %InternalNode{textnode |
|
[ %InternalNode{textnode |
|
||||||
@ -18,7 +18,7 @@ defmodule Outlook.InternalTree.RawInternalBasic do
|
|||||||
|> String.replace(~r/\.\.\.+/u, "…")
|
|> String.replace(~r/\.\.\.+/u, "…")
|
||||||
|> String.replace(~r/([[:upper:]])\./u, "\\1#{@nonperiodmarker}")
|
|> String.replace(~r/([[:upper:]])\./u, "\\1#{@nonperiodmarker}")
|
||||||
|> String.replace(~r/(\d)\.(\d)/u, "\\1#{@nonperiodmarker}\\2")
|
|> String.replace(~r/(\d)\.(\d)/u, "\\1#{@nonperiodmarker}\\2")
|
||||||
|> String.replace(~r|([.?!]["'”]?\s*)|u, "\\1#{@splitmarker}")
|
|> String.replace(~r|([.?!]["'”’]?\s*)|u, "\\1#{@splitmarker}")
|
||||||
|> String.replace(@nonperiodmarker, ".")
|
|> String.replace(@nonperiodmarker, ".")
|
||||||
} | set_split_markers(rest) ]
|
} | set_split_markers(rest) ]
|
||||||
end
|
end
|
||||||
|
|||||||
82
lib/outlook/internal_tree/tunit_modifications.ex
Normal file
82
lib/outlook/internal_tree/tunit_modifications.ex
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
defmodule Outlook.InternalTree.TunitModifications do
|
||||||
|
|
||||||
|
alias Outlook.InternalTree.{InternalNode,TranslationUnit}
|
||||||
|
|
||||||
|
def modifiers do
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
name: "unite_with_next",
|
||||||
|
fn: &unite_with_next/2,
|
||||||
|
label: "Unite with next",
|
||||||
|
description: "unite selected translation unit with (unselected) next"
|
||||||
|
},
|
||||||
|
# %{
|
||||||
|
# name: "split_tunit",
|
||||||
|
# fn: &split_tunit/2,
|
||||||
|
# label: "Split Translation unit",
|
||||||
|
# description: "split translation unit into two"
|
||||||
|
# }
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Modifier functions
|
||||||
|
|
||||||
|
defp unite_with_next(nodelist, tu_ids) when is_list(tu_ids) do
|
||||||
|
ids_to_process = Enum.reverse(tu_ids)
|
||||||
|
Enum.reduce(ids_to_process, nodelist, fn id, nodes -> unite_with_next(nodes, id) end)
|
||||||
|
end
|
||||||
|
defp unite_with_next(nodelist, tu_id) do
|
||||||
|
ind = Enum.find_index(nodelist, fn n -> n.nid == tu_id end)
|
||||||
|
nodes = Enum.slice(nodelist, ind, 2)
|
||||||
|
unite_with_next(nodelist, ind, nodes)
|
||||||
|
end
|
||||||
|
defp unite_with_next(nodelist, ind, [unit,next]) do
|
||||||
|
nunit = %TranslationUnit{unit | content: unit.content <> next.content}
|
||||||
|
nodelist
|
||||||
|
|> List.replace_at(ind, nunit)
|
||||||
|
|> List.delete_at(ind + 1)
|
||||||
|
end
|
||||||
|
defp unite_with_next(nodelist, _, [_]) do
|
||||||
|
nodelist
|
||||||
|
end
|
||||||
|
|
||||||
|
defp split_tunit(_nodelist, _tu_ids) do
|
||||||
|
end
|
||||||
|
|
||||||
|
# Function and helpers to apply modifiers
|
||||||
|
|
||||||
|
def apply_modifier([ %InternalNode{} = node | rest ], modifier, tu_ids) when node.type == :element do
|
||||||
|
content = case List.first(node.content) do
|
||||||
|
%TranslationUnit{} -> process_tunit_list(node.content, modifier, tu_ids)
|
||||||
|
_ -> apply_modifier(node.content, modifier, tu_ids)
|
||||||
|
end
|
||||||
|
[%InternalNode{node| content: content} | apply_modifier(rest, modifier, tu_ids)]
|
||||||
|
end
|
||||||
|
def apply_modifier([node | rest], modifier, tu_ids), do: [node | apply_modifier(rest, modifier, tu_ids)]
|
||||||
|
def apply_modifier([],_, _), do: []
|
||||||
|
|
||||||
|
def process_tunit_list(tunits, modifier, tu_ids) do
|
||||||
|
modi_fun = get_modi_fun(modifier)
|
||||||
|
ids_to_process = get_ids_to_process(tunits, tu_ids)
|
||||||
|
case length(ids_to_process) do
|
||||||
|
0 -> tunits
|
||||||
|
_ -> modi_fun.(tunits, ids_to_process)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def get_ids_to_process(tunits, tu_ids) do
|
||||||
|
present_ids = Enum.map(tunits, fn u -> u.nid end)
|
||||||
|
found_ids = MapSet.new(present_ids)
|
||||||
|
|> MapSet.intersection(MapSet.new(tu_ids))
|
||||||
|
|> MapSet.to_list()
|
||||||
|
# make sure to return ids in the order they occur in tunits
|
||||||
|
Enum.filter(present_ids, fn pres -> pres in found_ids end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_modi_fun(modifier) do
|
||||||
|
modifiers()
|
||||||
|
|> Enum.find(fn m -> m.name == modifier end)
|
||||||
|
|> Map.get(:fn)
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -8,7 +8,7 @@ implement to_param protocol (no more needed for Outlook.Translations.Translation
|
|||||||
alias Outlook.Translations.Translation
|
alias Outlook.Translations.Translation
|
||||||
alias Outlook.Articles.Article
|
alias Outlook.Articles.Article
|
||||||
alias Outlook.Authors.Author
|
alias Outlook.Authors.Author
|
||||||
alias Outlook.Public.Artikel
|
alias Outlook.Public.{Artikel,Autor}
|
||||||
|
|
||||||
import Ecto.Query, warn: false
|
import Ecto.Query, warn: false
|
||||||
alias Outlook.Repo
|
alias Outlook.Repo
|
||||||
@ -17,19 +17,17 @@ implement to_param protocol (no more needed for Outlook.Translations.Translation
|
|||||||
q = from t in Translation,
|
q = from t in Translation,
|
||||||
join: a in Article, on: t.article_id == a.id,
|
join: a in Article, on: t.article_id == a.id,
|
||||||
join: au in Author, on: a.author_id == au.id,
|
join: au in Author, on: a.author_id == au.id,
|
||||||
select: [
|
select: %Artikel{
|
||||||
title: t.title,
|
title: t.title,
|
||||||
date: t.date,
|
date: t.date,
|
||||||
teaser: t.teaser,
|
teaser: t.teaser,
|
||||||
id: t.id,
|
id: t.id,
|
||||||
date_org: a.date,
|
date_org: a.date,
|
||||||
autor_name: au.name,
|
autor_name: au.name,
|
||||||
],
|
},
|
||||||
where: t.public == true and t.language == ^language,
|
where: t.public == true and t.language == ^language,
|
||||||
order_by: [desc: t.date]
|
order_by: [desc: t.date]
|
||||||
Repo.all(q)
|
Repo.all(q)
|
||||||
# |> Enum.map(fn rec -> Enum.into(rec, %{}) end)
|
|
||||||
|> Enum.map(fn map -> struct(Artikel, map) end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_artikel!(artikel) when is_struct(artikel), do: get_artikel!(artikel.id)
|
def get_artikel!(artikel) when is_struct(artikel), do: get_artikel!(artikel.id)
|
||||||
@ -37,7 +35,7 @@ implement to_param protocol (no more needed for Outlook.Translations.Translation
|
|||||||
q = from t in Translation,
|
q = from t in Translation,
|
||||||
join: a in Article, on: t.article_id == a.id,
|
join: a in Article, on: t.article_id == a.id,
|
||||||
join: au in Author, on: a.author_id == au.id,
|
join: au in Author, on: a.author_id == au.id,
|
||||||
select: [
|
select: %Artikel{
|
||||||
title: t.title,
|
title: t.title,
|
||||||
date: t.date,
|
date: t.date,
|
||||||
public_content: t.public_content,
|
public_content: t.public_content,
|
||||||
@ -45,12 +43,12 @@ implement to_param protocol (no more needed for Outlook.Translations.Translation
|
|||||||
url_org: a.url,
|
url_org: a.url,
|
||||||
date_org: a.date,
|
date_org: a.date,
|
||||||
autor_name: au.name,
|
autor_name: au.name,
|
||||||
author_id: au.id
|
autor_id: au.id
|
||||||
],
|
},
|
||||||
where: t.id == ^id and t.public == true
|
where: t.id == ^id and t.public == true
|
||||||
case Repo.one(q) do
|
case Repo.one(q) do
|
||||||
nil -> {:error, "Artikel does not exist, or isn't public."}
|
nil -> {:error, "Artikel does not exist, or isn't public."}
|
||||||
artikel -> {:ok, struct(Artikel, artikel)}
|
artikel -> {:ok, artikel}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -69,7 +67,28 @@ implement to_param protocol (no more needed for Outlook.Translations.Translation
|
|||||||
end
|
end
|
||||||
|
|
||||||
def get_autor!(id) do
|
def get_autor!(id) do
|
||||||
Repo.get!(Author, id)
|
q = from au in Author,
|
||||||
|> Repo.preload([articles: [:translations]])
|
select: %Autor{
|
||||||
|
name: au.name,
|
||||||
|
description: au.description,
|
||||||
|
homepage_name: au.homepage_name,
|
||||||
|
homepage_url: au.homepage_url,
|
||||||
|
},
|
||||||
|
where: au.id == ^id
|
||||||
|
autor = Repo.one(q)
|
||||||
|
|
||||||
|
q2 = from a in Article,
|
||||||
|
join: t in Translation, on: t.article_id == a.id,
|
||||||
|
select: %Artikel{
|
||||||
|
title: t.title,
|
||||||
|
date: t.date,
|
||||||
|
teaser: t.teaser,
|
||||||
|
id: t.id,
|
||||||
|
date_org: a.date
|
||||||
|
},
|
||||||
|
where: a.author_id == ^id and t.public == true
|
||||||
|
artikel = Repo.all(q2)
|
||||||
|
|
||||||
|
%Autor{autor | artikel: artikel}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -11,7 +11,7 @@ defmodule Outlook.Public.Artikel do
|
|||||||
field :url_org, :string
|
field :url_org, :string
|
||||||
field :date_org, :utc_datetime
|
field :date_org, :utc_datetime
|
||||||
field :autor_name, :string
|
field :autor_name, :string
|
||||||
field :author_id, :integer
|
field :autor_id, :integer
|
||||||
field :teaser, :string
|
field :teaser, :string
|
||||||
# field :autor, Autor
|
# field :autor, Autor
|
||||||
end
|
end
|
||||||
|
|||||||
13
lib/outlook/public/autor.ex
Normal file
13
lib/outlook/public/autor.ex
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
defmodule Outlook.Public.Autor do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Outlook.Public.Artikel
|
||||||
|
|
||||||
|
embedded_schema do
|
||||||
|
field :name, :string
|
||||||
|
field :description, :string
|
||||||
|
field :homepage_name, :string
|
||||||
|
field :homepage_url, :string
|
||||||
|
has_many :artikel, Artikel
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -15,6 +15,7 @@ defmodule Outlook.Translations.Translation do
|
|||||||
field :public_content, :string
|
field :public_content, :string
|
||||||
field :title, :string
|
field :title, :string
|
||||||
field :unauthorized, :boolean, default: false
|
field :unauthorized, :boolean, default: false
|
||||||
|
field :remarks, :string
|
||||||
belongs_to :user, User
|
belongs_to :user, User
|
||||||
belongs_to :article, Article
|
belongs_to :article, Article
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ defmodule Outlook.Translations.Translation do
|
|||||||
@doc false
|
@doc false
|
||||||
def changeset(translation, attrs) do
|
def changeset(translation, attrs) do
|
||||||
translation
|
translation
|
||||||
|> cast(attrs, [:language, :title, :teaser, :date, :public, :unauthorized, :article_id, :public_content])
|
|> cast(attrs, [:language, :title, :teaser, :date, :public, :unauthorized, :article_id, :public_content, :remarks])
|
||||||
|> cast(attrs, [:content])
|
|> cast(attrs, [:content])
|
||||||
|> validate_required([:language, :title, :content, :date, :public, :unauthorized, :article_id])
|
|> validate_required([:language, :title, :content, :date, :public, :unauthorized, :article_id])
|
||||||
|> unique_constraint([:language, :article_id],
|
|> unique_constraint([:language, :article_id],
|
||||||
|
|||||||
@ -83,7 +83,7 @@ defmodule Outlook.Translators do
|
|||||||
|> IO.iodata_to_binary()
|
|> IO.iodata_to_binary()
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_translation(translation, tunit_ids) do
|
defp process_translation(translation, tunit_ids) do
|
||||||
tunit_map = translation
|
tunit_map = translation
|
||||||
|> Floki.parse_fragment!
|
|> Floki.parse_fragment!
|
||||||
|> Floki.find("tunit")
|
|> Floki.find("tunit")
|
||||||
|
|||||||
@ -54,10 +54,12 @@ defmodule Outlook.Translators.Deepl do
|
|||||||
)
|
)
|
||||||
response = Jason.decode!(response_raw.body, keys: :atoms)
|
response = Jason.decode!(response_raw.body, keys: :atoms)
|
||||||
|
|
||||||
|
require Logger
|
||||||
case response do
|
case response do
|
||||||
%{status: "done"} ->
|
%{status: "done"} ->
|
||||||
response
|
response
|
||||||
%{status: status} ->
|
%{status: status} ->
|
||||||
|
Logger.debug "Deepl response: #{response |> inspect}"
|
||||||
steps = Map.get(response, :seconds_remaining, 1) * 5
|
steps = Map.get(response, :seconds_remaining, 1) * 5
|
||||||
for n <- 0..steps do
|
for n <- 0..steps do
|
||||||
send(pid, {:progress, %{progress: 100 * n / steps, status: status}})
|
send(pid, {:progress, %{progress: 100 * n / steps, status: status}})
|
||||||
|
|||||||
@ -464,11 +464,11 @@ defmodule OutlookWeb.CoreComponents do
|
|||||||
<th class="relative p-0 pb-4"><span class="sr-only"><%= gettext("Actions") %></span></th>
|
<th class="relative p-0 pb-4"><span class="sr-only"><%= gettext("Actions") %></span></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="relative divide-y divide-zinc-100 border-t border-zinc-200 text-sm leading-6 text-zinc-700">
|
<tbody class="relative divide-y divide-zinc-100 border-t border-zinc-200 dark:border-zinc-500 text-sm leading-6 text-zinc-700 dark:text-zinc-400">
|
||||||
<tr
|
<tr
|
||||||
:for={row <- @rows}
|
:for={row <- @rows}
|
||||||
id={"#{@id}-#{Phoenix.Param.to_param(row)}"}
|
id={"#{@id}-#{Phoenix.Param.to_param(row)}"}
|
||||||
class="relative group hover:bg-zinc-50"
|
class="relative group hover:bg-zinc-50 dark:hover:bg-zinc-800 "
|
||||||
>
|
>
|
||||||
<td
|
<td
|
||||||
:for={{col, i} <- Enum.with_index(@col)}
|
:for={{col, i} <- Enum.with_index(@col)}
|
||||||
@ -476,11 +476,11 @@ defmodule OutlookWeb.CoreComponents do
|
|||||||
class={["p-0", @row_click && "hover:cursor-pointer"]}
|
class={["p-0", @row_click && "hover:cursor-pointer"]}
|
||||||
>
|
>
|
||||||
<div :if={i == 0}>
|
<div :if={i == 0}>
|
||||||
<span class="absolute h-full w-4 top-0 -left-4 group-hover:bg-zinc-50 sm:rounded-l-xl" />
|
<span class="absolute h-full w-4 top-0 -left-4 group-hover:bg-zinc-50 dark:group-hover:bg-zinc-800 sm:rounded-l-xl" />
|
||||||
<span class="absolute h-full w-4 top-0 -right-4 group-hover:bg-zinc-50 sm:rounded-r-xl" />
|
<span class="absolute h-full w-4 top-0 -right-4 group-hover:bg-zinc-50 dark:group-hover:bg-zinc-800 sm:rounded-r-xl" />
|
||||||
</div>
|
</div>
|
||||||
<div class="block py-4 pr-6">
|
<div class="block py-4 pr-6">
|
||||||
<span class={["relative", i == 0 && "font-semibold text-zinc-900"]}>
|
<span class={["relative", i == 0 && "font-semibold text-zinc-900 dark:text-zinc-300"]}>
|
||||||
<%= render_slot(col, row) %>
|
<%= render_slot(col, row) %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -489,7 +489,7 @@ defmodule OutlookWeb.CoreComponents do
|
|||||||
<div class="relative whitespace-nowrap py-4 text-right text-sm font-medium">
|
<div class="relative whitespace-nowrap py-4 text-right text-sm font-medium">
|
||||||
<span
|
<span
|
||||||
:for={action <- @action}
|
:for={action <- @action}
|
||||||
class="relative ml-4 font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
|
class="relative ml-4 font-semibold leading-6 text-zinc-900 hover:text-zinc-700 dark:text-zinc-500 dark:hover:text-zinc-400"
|
||||||
>
|
>
|
||||||
<%= render_slot(action, row) %>
|
<%= render_slot(action, row) %>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -9,7 +9,7 @@ defmodule OutlookWeb.DarkModeComponent do
|
|||||||
|
|
||||||
def dark_mode_widget(assigns) do
|
def dark_mode_widget(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div id="dark-mode-widget" class="absolute flex w-full justify-between p-0" phx-hook="dark_mode_widget">
|
<div id="dark-mode-widget" class="relative flex w-full justify-between p-0" phx-hook="dark_mode_widget">
|
||||||
<div class="flex"></div>
|
<div class="flex"></div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<button class="p-2" type="button">
|
<button class="p-2" type="button">
|
||||||
@ -41,7 +41,7 @@ defmodule OutlookWeb.DarkModeComponent do
|
|||||||
|
|
||||||
def breakpoint_indicator(assigns) do
|
def breakpoint_indicator(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="absolute p-1 bg-white text-black dark:bg-black dark:text-white">
|
<div class="absolute mt-5 ml-40 bg-white text-black dark:bg-black dark:text-white">
|
||||||
<span class="sm:hidden">xs</span>
|
<span class="sm:hidden">xs</span>
|
||||||
<span class="hidden sm:inline md:hidden">sm</span>
|
<span class="hidden sm:inline md:hidden">sm</span>
|
||||||
<span class="hidden md:inline lg:hidden">md</span>
|
<span class="hidden md:inline lg:hidden">md</span>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ defmodule OutlookWeb.HtmlTreeComponent do
|
|||||||
use Phoenix.Component
|
use Phoenix.Component
|
||||||
# use OutlookWeb, :html
|
# use OutlookWeb, :html
|
||||||
import OutlookWeb.CoreComponents
|
import OutlookWeb.CoreComponents
|
||||||
|
import OutlookWeb.ViewHelpers
|
||||||
|
|
||||||
alias Phoenix.LiveView.JS
|
alias Phoenix.LiveView.JS
|
||||||
|
|
||||||
@ -15,10 +16,14 @@ defmodule OutlookWeb.HtmlTreeComponent do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def attributes(assigns) do
|
def attributes(assigns) do
|
||||||
~H" <%= @name %>="<%= @value %>""
|
~H" <%= @name %>="<%= elipsed_text(@value, 16) %>""
|
||||||
end
|
end
|
||||||
|
|
||||||
def tnode(%{node: %{status: _}} = assigns), do: ~H"<%= String.slice(@node.content, 0..20) %><%= if String.length(@node.content) > 20 do %>...<% end %><br>"
|
def tnode(%{node: %{status: _}} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<span title={@node.content} {@node.eph.attributes}><%= elipsed_text(@node.content, 30) %></span><br>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
def tnode(assigns) when assigns.node.type == :element do
|
def tnode(assigns) when assigns.node.type == :element do
|
||||||
~H"""
|
~H"""
|
||||||
@ -32,7 +37,7 @@ defmodule OutlookWeb.HtmlTreeComponent do
|
|||||||
|
|
||||||
def tnode(assigns) when assigns.node.type == :text do
|
def tnode(assigns) when assigns.node.type == :text do
|
||||||
~H"""
|
~H"""
|
||||||
"<%= String.slice(@node.content, 0..35) %><%= if String.length(@node.content) > 35 do %>..."<% end %><br>
|
"<%= elipsed_text(@node.content, 30) %><br>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -1,43 +1,9 @@
|
|||||||
<header class="px-4 sm:px-6 lg:px-8">
|
<header class="">
|
||||||
<div class="flex items-center justify-between border-b border-zinc-100 py-3">
|
<.breakpoint_indicator :if={Mix.env == :dev} />
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<a href="/">
|
|
||||||
<svg viewBox="0 0 71 48" class="h-6" aria-hidden="true">
|
|
||||||
<path
|
|
||||||
d="m26.371 33.477-.552-.1c-3.92-.729-6.397-3.1-7.57-6.829-.733-2.324.597-4.035 3.035-4.148 1.995-.092 3.362 1.055 4.57 2.39 1.557 1.72 2.984 3.558 4.514 5.305 2.202 2.515 4.797 4.134 8.347 3.634 3.183-.448 5.958-1.725 8.371-3.828.363-.316.761-.592 1.144-.886l-.241-.284c-2.027.63-4.093.841-6.205.735-3.195-.16-6.24-.828-8.964-2.582-2.486-1.601-4.319-3.746-5.19-6.611-.704-2.315.736-3.934 3.135-3.6.948.133 1.746.56 2.463 1.165.583.493 1.143 1.015 1.738 1.493 2.8 2.25 6.712 2.375 10.265-.068-5.842-.026-9.817-3.24-13.308-7.313-1.366-1.594-2.7-3.216-4.095-4.785-2.698-3.036-5.692-5.71-9.79-6.623C12.8-.623 7.745.14 2.893 2.361 1.926 2.804.997 3.319 0 4.149c.494 0 .763.006 1.032 0 2.446-.064 4.28 1.023 5.602 3.024.962 1.457 1.415 3.104 1.761 4.798.513 2.515.247 5.078.544 7.605.761 6.494 4.08 11.026 10.26 13.346 2.267.852 4.591 1.135 7.172.555ZM10.751 3.852c-.976.246-1.756-.148-2.56-.962 1.377-.343 2.592-.476 3.897-.528-.107.848-.607 1.306-1.336 1.49Zm32.002 37.924c-.085-.626-.62-.901-1.04-1.228-1.857-1.446-4.03-1.958-6.333-2-1.375-.026-2.735-.128-4.031-.61-.595-.22-1.26-.505-1.244-1.272.015-.78.693-1 1.31-1.184.505-.15 1.026-.247 1.6-.382-1.46-.936-2.886-1.065-4.787-.3-2.993 1.202-5.943 1.06-8.926-.017-1.684-.608-3.179-1.563-4.735-2.408l-.043.03a2.96 2.96 0 0 0 .04-.029c-.038-.117-.107-.12-.197-.054l.122.107c1.29 2.115 3.034 3.817 5.004 5.271 3.793 2.8 7.936 4.471 12.784 3.73A66.714 66.714 0 0 1 37 40.877c1.98-.16 3.866.398 5.753.899Zm-9.14-30.345c-.105-.076-.206-.266-.42-.069 1.745 2.36 3.985 4.098 6.683 5.193 4.354 1.767 8.773 2.07 13.293.51 3.51-1.21 6.033-.028 7.343 3.38.19-3.955-2.137-6.837-5.843-7.401-2.084-.318-4.01.373-5.962.94-5.434 1.575-10.485.798-15.094-2.553Zm27.085 15.425c.708.059 1.416.123 2.124.185-1.6-1.405-3.55-1.517-5.523-1.404-3.003.17-5.167 1.903-7.14 3.972-1.739 1.824-3.31 3.87-5.903 4.604.043.078.054.117.066.117.35.005.699.021 1.047.005 3.768-.17 7.317-.965 10.14-3.7.89-.86 1.685-1.817 2.544-2.71.716-.746 1.584-1.159 2.645-1.07Zm-8.753-4.67c-2.812.246-5.254 1.409-7.548 2.943-1.766 1.18-3.654 1.738-5.776 1.37-.374-.066-.75-.114-1.124-.17l-.013.156c.135.07.265.151.405.207.354.14.702.308 1.07.395 4.083.971 7.992.474 11.516-1.803 2.221-1.435 4.521-1.707 7.013-1.336.252.038.503.083.756.107.234.022.479.255.795.003-2.179-1.574-4.526-2.096-7.094-1.872Zm-10.049-9.544c1.475.051 2.943-.142 4.486-1.059-.452.04-.643.04-.827.076-2.126.424-4.033-.04-5.733-1.383-.623-.493-1.257-.974-1.889-1.457-2.503-1.914-5.374-2.555-8.514-2.5.05.154.054.26.108.315 3.417 3.455 7.371 5.836 12.369 6.008Zm24.727 17.731c-2.114-2.097-4.952-2.367-7.578-.537 1.738.078 3.043.632 4.101 1.728.374.388.763.768 1.182 1.106 1.6 1.29 4.311 1.352 5.896.155-1.861-.726-1.861-.726-3.601-2.452Zm-21.058 16.06c-1.858-3.46-4.981-4.24-8.59-4.008a9.667 9.667 0 0 1 2.977 1.39c.84.586 1.547 1.311 2.243 2.055 1.38 1.473 3.534 2.376 4.962 2.07-.656-.412-1.238-.848-1.592-1.507Zm17.29-19.32c0-.023.001-.045.003-.068l-.006.006.006-.006-.036-.004.021.018.012.053Zm-20 14.744a7.61 7.61 0 0 0-.072-.041.127.127 0 0 0 .015.043c.005.008.038 0 .058-.002Zm-.072-.041-.008-.034-.008.01.008-.01-.022-.006.005.026.024.014Z"
|
|
||||||
fill="#FD4F00"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
<p class="rounded-full bg-brand/5 px-2 text-[0.8125rem] font-medium leading-6 text-brand">
|
|
||||||
v1.7
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<a
|
|
||||||
href="https://twitter.com/elixirphoenix"
|
|
||||||
class="text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
|
|
||||||
>
|
|
||||||
@elixirphoenix
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://github.com/phoenixframework/phoenix"
|
|
||||||
class="text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
|
|
||||||
>
|
|
||||||
GitHub
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://hexdocs.pm/phoenix/overview.html"
|
|
||||||
class="rounded-lg bg-zinc-100 px-2 py-1 text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:bg-zinc-200/80 active:text-zinc-900/70"
|
|
||||||
>
|
|
||||||
Get Started <span aria-hidden="true">→</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<.dark_mode_widget />
|
<.dark_mode_widget />
|
||||||
</header>
|
</header>
|
||||||
<main class="px-4 py-20 sm:px-6 lg:px-8">
|
<main class="px-4 sm:px-6 lg:px-8 lg:mx-auto h-screen">
|
||||||
<div class="mx-auto max-w-4xl">
|
<div class="mx-auto max-w-4xl h-fit">
|
||||||
<.flash kind={:info} title="Success!" flash={@flash} />
|
<.flash kind={:info} title="Success!" flash={@flash} />
|
||||||
<.flash kind={:error} title="Error!" flash={@flash} />
|
<.flash kind={:error} title="Error!" flash={@flash} />
|
||||||
<.flash
|
<.flash
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<.dark_mode_widget />
|
<.dark_mode_widget />
|
||||||
</header>
|
</header>
|
||||||
<main class="px-2 py-4 sm:px-6 lg:px-8">
|
<main class="px-2 sm:px-6 lg:px-8">
|
||||||
<div class="mx-auto max-w-xl">
|
<div class="mx-auto max-w-xl">
|
||||||
<.flash kind={:info} title="Success!" flash={@flash} />
|
<.flash kind={:info} title="Success!" flash={@flash} />
|
||||||
<.flash kind={:error} title="Error!" flash={@flash} />
|
<.flash kind={:error} title="Error!" flash={@flash} />
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-white text-stone-900 dark:bg-stone-900 dark:text-stone-100 antialiased max-h-screen">
|
<body class="bg-white text-stone-900 dark:bg-stone-900 dark:text-stone-100 antialiased max-h-screen">
|
||||||
<ul>
|
<ul class="absolute">
|
||||||
<%= if @current_user do %>
|
<%= if @current_user do %>
|
||||||
<li>
|
<li>
|
||||||
<%= @current_user.email %>
|
<%= @current_user.email %>
|
||||||
|
|||||||
@ -9,12 +9,12 @@ defmodule OutlookWeb.TunitEditorComponent do
|
|||||||
|
|
||||||
def tunit_editor(assigns) do
|
def tunit_editor(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div id="translation-unit-editor" phx-nohook="tunit_editor">
|
<div id="translation-unit-editor" phx-no-hook="tunit_editor" phx-target={@target}>
|
||||||
<%!-- <div class="h-48 p-2 border border-slate-500 rounded" contenteditable phx-no-change="update_current_tunit">
|
<%!-- <div class="h-48 p-2 border border-slate-500 rounded" contenteditable phx-no-change="update_current_tunit">
|
||||||
<%= @current_tunit.content |> raw %>
|
<%= @current_tunit.content |> raw %>
|
||||||
</div> --%>
|
</div> --%>
|
||||||
<form phx-change="update_current_tunit" phx-target={@target}>
|
<form phx-change="update_current_tunit" phx-target={@target}>
|
||||||
<textarea name="content" class="h-48 rounded border-slate-500 resize-none bg-transparent w-full"
|
<textarea id="tunit-editor-content" name="content" class="h-96 rounded border-slate-500 resize-none bg-transparent w-full"
|
||||||
disabled={!@current_tunit.status}><%= @current_tunit.content %></textarea>
|
disabled={!@current_tunit.status}><%= @current_tunit.content %></textarea>
|
||||||
</form>
|
</form>
|
||||||
<.status_selector target={@target} disabled={!@current_tunit.status} tunit={@current_tunit} />
|
<.status_selector target={@target} disabled={!@current_tunit.status} tunit={@current_tunit} />
|
||||||
@ -24,7 +24,7 @@ defmodule OutlookWeb.TunitEditorComponent do
|
|||||||
|
|
||||||
defp statuses() do
|
defp statuses() do
|
||||||
[ {:untranslated, "bg-red-800"},
|
[ {:untranslated, "bg-red-800"},
|
||||||
{:passable, "bg-amber-500"},
|
{:passable, "bg-amber-500/70"},
|
||||||
{:done, "bg-green-700"} ]
|
{:done, "bg-green-700"} ]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<header class="mb-6">
|
<header class="mb-6">
|
||||||
<h1 class="text-lg font-semibold leading-tight text-stone-800 dark:text-stone-200"><%= @artikel.title %></h1>
|
<h1 class="text-lg font-semibold leading-tight text-stone-800 dark:text-stone-200"><%= @artikel.title %></h1>
|
||||||
<p class="my-2"><.link href={~p"/autoren/#{@artikel.author_id}"}><%= @artikel.autor_name %></.link>
|
<p class="my-2"><.link href={~p"/autoren/#{@artikel.autor_id}"}><%= @artikel.autor_name %></.link>
|
||||||
— <%= Calendar.strftime(@artikel.date_org, "%d.%m.%Y") %></p>
|
— <%= Calendar.strftime(@artikel.date_org, "%d.%m.%Y") %></p>
|
||||||
<div>Original Artikel:
|
<div>Original Artikel:
|
||||||
<.link class="hover:text-sky-700" href={@artikel.url_org} >
|
<.link class="hover:text-sky-700" href={@artikel.url_org} >
|
||||||
@ -14,4 +14,4 @@
|
|||||||
|
|
||||||
<div class="article w-full mx-auto max-w-xs"><%= @artikel.public_content |> raw %></div>
|
<div class="article w-full mx-auto max-w-xs"><%= @artikel.public_content |> raw %></div>
|
||||||
|
|
||||||
<.back navigate={~p"/autoren/#{@artikel.author_id}"}>Back to Autor</.back>
|
<.back navigate={~p"/autoren/#{@artikel.autor_id}"}>Back to Autor</.back>
|
||||||
|
|||||||
@ -4,10 +4,6 @@
|
|||||||
<:subtitle><.link href={@autor.homepage_url}><%= @autor.homepage_name %></.link></:subtitle>
|
<:subtitle><.link href={@autor.homepage_url}><%= @autor.homepage_name %></.link></:subtitle>
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
|
<.artikel :for={artikel <- @autor.artikel} artikel={artikel} show_autor={false} />
|
||||||
|
|
||||||
<%= for article <- @autor.articles do %>
|
|
||||||
<.artikel :for={translation <- article.translations} artikel={translation} show_autor={false} />
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<.back navigate={~p"/autoren"}>Back to autoren</.back>
|
<.back navigate={~p"/autoren"}>Back to autoren</.back>
|
||||||
|
|||||||
@ -26,6 +26,7 @@ defmodule OutlookWeb.ArticleLive.FormComponent do
|
|||||||
<.input field={{f, :language}} type="select" label="language"
|
<.input field={{f, :language}} type="select" label="language"
|
||||||
options={Application.get_env(:outlook,:deepl)[:source_langs]} />
|
options={Application.get_env(:outlook,:deepl)[:source_langs]} />
|
||||||
<.input field={{f, :date}} type="datetime-local" label="date" />
|
<.input field={{f, :date}} type="datetime-local" label="date" />
|
||||||
|
<.input field={{f, :remarks}} type="textarea" label="remarks" class="h-28" />
|
||||||
<:actions>
|
<:actions>
|
||||||
<.button phx-disable-with="Saving...">Save Article</.button>
|
<.button phx-disable-with="Saving...">Save Article</.button>
|
||||||
</:actions>
|
</:actions>
|
||||||
|
|||||||
24
lib/outlook_web/live/article_live/menu_component.ex
Normal file
24
lib/outlook_web/live/article_live/menu_component.ex
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
defmodule OutlookWeb.ArticleLive.MenuComponent do
|
||||||
|
use OutlookWeb, :live_component
|
||||||
|
|
||||||
|
attr :entries, :list
|
||||||
|
# attr :target, :integer, default: 0 # @myself of the Live(View|Component) where the handlers reside
|
||||||
|
attr :handler, :string
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def render(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div>
|
||||||
|
<.menu_item :for={entry <- @entries} entry={entry} handler={@handler} />
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
def menu_item(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div title={@entry.description} phx-click={@handler} phx-value-modifier={@entry.name}>
|
||||||
|
<%= @entry.label %>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -17,6 +17,7 @@ defmodule OutlookWeb.ArticleLive.New do
|
|||||||
|> 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, [])
|
||||||
|
|> assign(:selected_tunits, [])
|
||||||
|> assign(:step, :import_raw_html)}
|
|> assign(:step, :import_raw_html)}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ defmodule OutlookWeb.ArticleLive.New do
|
|||||||
article = %Article{author_id: author.id}
|
article = %Article{author_id: author.id}
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|
|> assign(:menu_entries, InternalTree.tunit_modifiers()) # REMOVE ME!
|
||||||
|> assign(:author, author)
|
|> assign(:author, author)
|
||||||
|> assign(:article, article)}
|
|> assign(:article, article)}
|
||||||
end
|
end
|
||||||
@ -43,7 +45,9 @@ defmodule OutlookWeb.ArticleLive.New do
|
|||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> assign(:raw_internal_tree,
|
|> assign(:raw_internal_tree,
|
||||||
HtmlPreparations.convert_raw_html_input(raw_html_input_params["content"]))
|
HtmlPreparations.convert_raw_html_input(raw_html_input_params["content"])
|
||||||
|
|> InternalTree.garnish(%{})
|
||||||
|
)
|
||||||
|> assign(:step, :review_raw_internaltree)}
|
|> assign(:step, :review_raw_internaltree)}
|
||||||
false ->
|
false ->
|
||||||
{:noreply, assign(socket, :changeset, changeset)}
|
{:noreply, assign(socket, :changeset, changeset)}
|
||||||
@ -54,10 +58,42 @@ defmodule OutlookWeb.ArticleLive.New do
|
|||||||
def handle_event("approve_raw_internaltree", _, socket) do
|
def handle_event("approve_raw_internaltree", _, socket) do
|
||||||
socket = socket
|
socket = socket
|
||||||
|> assign(:raw_internal_tree,
|
|> assign(:raw_internal_tree,
|
||||||
InternalTree.partition_text(socket.assigns.raw_internal_tree))
|
InternalTree.partition_text(socket.assigns.raw_internal_tree)
|
||||||
|
|> InternalTree.garnish(%{tunits: %{class: :tunit}})
|
||||||
|
|> InternalTree.add_phx_click_event(
|
||||||
|
nodes: :tunits,
|
||||||
|
click: "toggle_selected_tunit")
|
||||||
|
)
|
||||||
|
|> assign(:menu_entries, InternalTree.tunit_modifiers())
|
||||||
{:noreply, socket |> assign(:step, :review_translation_units)}
|
{:noreply, socket |> assign(:step, :review_translation_units)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("toggle_selected_tunit", %{"nid" => tunit_id}, socket) do
|
||||||
|
{:noreply, toggle_selected_tunit(socket, tunit_id)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp toggle_selected_tunit(socket, tunit_id) do
|
||||||
|
before = socket.assigns.selected_tunits
|
||||||
|
selected_tunits = case Enum.member?(before, tunit_id) do
|
||||||
|
false -> List.insert_at(before, -1, tunit_id)
|
||||||
|
true -> List.delete(before, tunit_id)
|
||||||
|
end
|
||||||
|
fun = fn n -> n.nid in selected_tunits && "yes" || "no" end
|
||||||
|
socket
|
||||||
|
|> assign(:raw_internal_tree, InternalTree.garnish(socket.assigns.raw_internal_tree, %{tunits: %{selected: fun}}))
|
||||||
|
|> assign(:selected_tunits, selected_tunits)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("modify_tunits", %{"modifier" => modifier}, socket) do
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:raw_internal_tree,
|
||||||
|
InternalTree.modify_tunits(socket.assigns.raw_internal_tree, modifier, socket.assigns.selected_tunits)
|
||||||
|
)}
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("approve_translation_units", _, socket) do
|
def handle_event("approve_translation_units", _, socket) do
|
||||||
{:noreply, socket |> assign(:step, :final_form)}
|
{:noreply, socket |> assign(:step, :final_form)}
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<.import_raw_html :if={@step == :import_raw_html} changeset={@changeset} />
|
<.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_raw_internaltree :if={@step == :review_raw_internaltree} raw_internal_tree={@raw_internal_tree} />
|
||||||
<.review_translation_units :if={@step == :review_translation_units} />
|
<.review_translation_units :if={@step == :review_translation_units} raw_internal_tree={@raw_internal_tree} menu_entries={@menu_entries} />
|
||||||
<.live_component
|
<.live_component
|
||||||
:if={@step == :final_form}
|
:if={@step == :final_form}
|
||||||
module={OutlookWeb.ArticleLive.FormComponent}
|
module={OutlookWeb.ArticleLive.FormComponent}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ defmodule OutlookWeb.ArticleLive.NewComponents do
|
|||||||
<div>Review Raw InternalTree</div>
|
<div>Review Raw InternalTree</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div id="html-preview" class="article">
|
<div id="html-preview" class="article">
|
||||||
<%= InternalTree.render_html_preview(@raw_internal_tree) |> raw %>
|
<.render_doc tree={@raw_internal_tree} ></.render_doc>
|
||||||
</div>
|
</div>
|
||||||
<div id="html-tree">
|
<div id="html-tree">
|
||||||
<.render_tree tree={@raw_internal_tree} ></.render_tree>
|
<.render_tree tree={@raw_internal_tree} ></.render_tree>
|
||||||
@ -44,6 +44,17 @@ defmodule OutlookWeb.ArticleLive.NewComponents do
|
|||||||
def review_translation_units(assigns) do
|
def review_translation_units(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div>Review Translation Units</div>
|
<div>Review Translation Units</div>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div id="html-tree" class="article w-96 overflow-auto whitespace-nowrap">
|
||||||
|
<.render_tree tree={@raw_internal_tree} ></.render_tree>
|
||||||
|
</div>
|
||||||
|
<div id="partition-preview" class="article show-boundary overflow-auto h-full">
|
||||||
|
<.render_doc tree={@raw_internal_tree} ></.render_doc>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<.live_component module={OutlookWeb.ArticleLive.MenuComponent} entries={@menu_entries} handler="modify_tunits" id={:review_tunits} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<.button phx-click="approve_translation_units">Continue</.button>
|
<.button phx-click="approve_translation_units">Continue</.button>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|||||||
@ -40,6 +40,10 @@
|
|||||||
<a href="#" class="hide-link" phx-click={JS.remove_class("show-boundary", to: ".article")}>hide boundaries</a>
|
<a href="#" class="hide-link" phx-click={JS.remove_class("show-boundary", to: ".article")}>hide boundaries</a>
|
||||||
<.render_doc tree={@article_content} />
|
<.render_doc tree={@article_content} />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="my-4">
|
||||||
|
<div>Remarks:</div>
|
||||||
|
<%= @article.remarks |> tidy_raw %>
|
||||||
|
</div>
|
||||||
<div class="h-10" />
|
<div class="h-10" />
|
||||||
|
|
||||||
<.link class="text-sm font-semibold" navigate={~p"/translations/new?article_id=#{@article.id}"}>New Translation</.link>
|
<.link class="text-sm font-semibold" navigate={~p"/translations/new?article_id=#{@article.id}"}>New Translation</.link>
|
||||||
|
|||||||
@ -8,12 +8,13 @@ defmodule OutlookWeb.TranslationLive.FormComponent do
|
|||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="flex gap-8 max-h-fit">
|
<div class="flex gap-8 max-h-fit">
|
||||||
<div class="basis-1/2 overflow-auto">
|
<div class="basis-1/2 overflow-auto" id="translation-form-container" target="@myself" phx-hook="translation_form">
|
||||||
<.header>
|
<.header>
|
||||||
<%= @title %>
|
<%= @title %>
|
||||||
<:subtitle>Use this form to manage translation records in your database.</:subtitle>
|
<:subtitle>Use this form to manage translation records in your database.</:subtitle>
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
|
<div phx-click={JS.toggle(to: ".more-fields")} class="cursor-pointer">more/less fields</div>
|
||||||
<.simple_form
|
<.simple_form
|
||||||
:let={f}
|
:let={f}
|
||||||
for={@changeset}
|
for={@changeset}
|
||||||
@ -23,29 +24,34 @@ defmodule OutlookWeb.TranslationLive.FormComponent do
|
|||||||
phx-submit="save"
|
phx-submit="save"
|
||||||
>
|
>
|
||||||
<.input field={{f, :article_id}} type="hidden" />
|
<.input field={{f, :article_id}} type="hidden" />
|
||||||
|
<div class="more-fields">
|
||||||
<.input field={{f, :language}} type="select" label="language"
|
<.input field={{f, :language}} type="select" label="language"
|
||||||
options={Application.get_env(:outlook,:deepl)[:target_langs]} />
|
options={Application.get_env(:outlook,:deepl)[:target_langs]} />
|
||||||
|
</div>
|
||||||
<.input field={{f, :title}} type="text" label="title" />
|
<.input field={{f, :title}} type="text" label="title" />
|
||||||
|
<div class="more-fields">
|
||||||
<.input field={{f, :teaser}} type="textarea" label="teaser" class="h-28" />
|
<.input field={{f, :teaser}} type="textarea" label="teaser" class="h-28" />
|
||||||
<.input field={{f, :date}} type="datetime-local" label="date" />
|
<.input field={{f, :date}} type="datetime-local" label="date" />
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<.input field={{f, :public}} type="checkbox" label="public" />
|
<.input field={{f, :public}} type="checkbox" label="public" />
|
||||||
<.input field={{f, :unauthorized}} type="checkbox" label="unauthorized" />
|
<.input field={{f, :unauthorized}} type="checkbox" label="unauthorized" />
|
||||||
</div>
|
</div>
|
||||||
|
<.input field={{f, :remarks}} type="textarea" label="remarks" class="h-28" />
|
||||||
|
</div>
|
||||||
<input type="hidden" id="continue_edit" name="continue_edit" value="false" />
|
<input type="hidden" id="continue_edit" name="continue_edit" value="false" />
|
||||||
<input type="hidden" id="publish" name="publish" value="false" />
|
<input type="hidden" id="publish" name="publish" value="false" />
|
||||||
<:actions>
|
<:actions>
|
||||||
<.button phx-click={JS.set_attribute({"value", "false"}, to: "#continue_edit") |> JS.set_attribute({"value", "false"}, to: "#publish")}
|
<.button phx-click={JS.set_attribute({"value", "false"}, to: "#continue_edit") |> JS.set_attribute({"value", "false"}, to: "#publish")}
|
||||||
phx-disable-with="Saving...">Save Translation</.button>
|
id="save-button" phx-disable-with="Saving...">Save Translation</.button>
|
||||||
<.button phx-click={JS.set_attribute({"value", "false"}, to: "#continue_edit") |> JS.set_attribute({"value", "true"}, to: "#publish")}
|
<.button phx-click={JS.set_attribute({"value", "false"}, to: "#continue_edit") |> JS.set_attribute({"value", "true"}, to: "#publish")}
|
||||||
phx-disable-with="Saving...">Save and Publish</.button>
|
id="save-publish-button" phx-disable-with="Saving...">Save and Publish</.button>
|
||||||
<.button phx-click={JS.set_attribute({"value", "true"}, to: "#continue_edit") |> JS.set_attribute({"value", "false"}, to: "#publish")}
|
<.button phx-click={JS.set_attribute({"value", "true"}, to: "#continue_edit") |> JS.set_attribute({"value", "false"}, to: "#publish")}
|
||||||
phx-disable-with="Saving...">Save and Edit</.button>
|
id="save-edit-button" phx-disable-with="Saving...">Save and Edit</.button>
|
||||||
</:actions>
|
</:actions>
|
||||||
</.simple_form>
|
</.simple_form>
|
||||||
<.tunit_editor current_tunit={@current_tunit} target={@myself} />
|
<.tunit_editor current_tunit={@current_tunit} target={@myself} />
|
||||||
</div>
|
</div>
|
||||||
<div class="article basis-1/2 max-h-screen overflow-auto">
|
<div class="article article-preview basis-1/2 max-h-screen overflow-auto">
|
||||||
<.button phx-disable-with="Translating..." phx-click="translate-deepl" phx-target={@myself}
|
<.button phx-disable-with="Translating..." phx-click="translate-deepl" phx-target={@myself}
|
||||||
data-confirm-not="Are you sure? All previously translated text will be lost.">Translate with Deepl</.button>
|
data-confirm-not="Are you sure? All previously translated text will be lost.">Translate with Deepl</.button>
|
||||||
<progress :if={@deepl_progress} max="100" value={@deepl_progress} />
|
<progress :if={@deepl_progress} max="100" value={@deepl_progress} />
|
||||||
@ -63,6 +69,7 @@ defmodule OutlookWeb.TranslationLive.FormComponent do
|
|||||||
socket
|
socket
|
||||||
|> assign(assigns)
|
|> assign(assigns)
|
||||||
|> assign(:current_tunit, %TranslationUnit{status: nil})
|
|> assign(:current_tunit, %TranslationUnit{status: nil})
|
||||||
|
|> assign(:tunit_ids, InternalTree.get_tunit_ids(translation.article.content))
|
||||||
|> assign(:changeset, changeset)
|
|> assign(:changeset, changeset)
|
||||||
|> assign_article_tree(translation)
|
|> assign_article_tree(translation)
|
||||||
|> assign(:deepl_progress, nil)}
|
|> assign(:deepl_progress, nil)}
|
||||||
@ -118,11 +125,33 @@ defmodule OutlookWeb.TranslationLive.FormComponent do
|
|||||||
{:noreply, socket |> assign(:current_tunit, tunit)}
|
{:noreply, socket |> assign(:current_tunit, tunit)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("select_current_tunit", %{"nid" => nid}, socket) do
|
def handle_event("select_tunit_by_nid", %{"nid" => nid}, socket) do
|
||||||
{:noreply,
|
{:noreply, change_tunit(socket, nid)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("select_next_tunit", _, socket) do
|
||||||
|
{:noreply, select_next_tunit(socket, &Kernel.+/2)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("select_previous_tunit", _, socket) do
|
||||||
|
{:noreply, select_next_tunit(socket, &Kernel.-/2)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp select_next_tunit(socket, direction) do
|
||||||
|
tunit_ids = socket.assigns.tunit_ids
|
||||||
|
current_tunit_nid = socket.assigns.current_tunit.status && socket.assigns.current_tunit.nid || List.last(tunit_ids)
|
||||||
|
index_current = Enum.find_index(tunit_ids, fn nid -> nid == current_tunit_nid end)
|
||||||
|
index_next = direction.(index_current, 1) |> Integer.mod(length(tunit_ids))
|
||||||
|
next_nid = Enum.at(tunit_ids, index_next)
|
||||||
|
change_tunit(socket, next_nid)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp change_tunit(socket, nid) do
|
||||||
|
fun = fn n -> n.nid == nid && "yes" || "no" end
|
||||||
socket
|
socket
|
||||||
|
|> assign(:article_tree, InternalTree.garnish(socket.assigns.article_tree, %{tunits: %{current: fun}}))
|
||||||
|> update_translation_with_current_tunit(socket.assigns.current_tunit.status)
|
|> update_translation_with_current_tunit(socket.assigns.current_tunit.status)
|
||||||
|> assign(:current_tunit, socket.assigns.translation_content[nid])}
|
|> assign(:current_tunit, socket.assigns.translation_content[nid])
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "updating on browser events"
|
@doc "updating on browser events"
|
||||||
@ -146,7 +175,7 @@ defmodule OutlookWeb.TranslationLive.FormComponent do
|
|||||||
:article_tree,
|
:article_tree,
|
||||||
InternalTree.add_phx_click_event(translation.article.content,
|
InternalTree.add_phx_click_event(translation.article.content,
|
||||||
nodes: :tunits,
|
nodes: :tunits,
|
||||||
click: "select_current_tunit",
|
click: "select_tunit_by_nid",
|
||||||
target: socket.assigns.myself)
|
target: socket.assigns.myself)
|
||||||
|> InternalTree.garnish(%{tunits: %{class: "tunit"}}))
|
|> InternalTree.garnish(%{tunits: %{class: "tunit"}}))
|
||||||
end
|
end
|
||||||
@ -187,6 +216,7 @@ defmodule OutlookWeb.TranslationLive.FormComponent do
|
|||||||
|
|
||||||
defp continue_edit(socket, :edit, %{"continue_edit" => "true"}) do
|
defp continue_edit(socket, :edit, %{"continue_edit" => "true"}) do
|
||||||
socket
|
socket
|
||||||
|
|> assign(:translation, Translations.get_translation!(socket.assigns.translation.id))
|
||||||
end
|
end
|
||||||
defp continue_edit(socket, :new, %{"continue_edit" => "true"} = params) do
|
defp continue_edit(socket, :new, %{"continue_edit" => "true"} = params) do
|
||||||
socket |> push_patch(to: ~p(/translations/#{params["id"]}/edit))
|
socket |> push_patch(to: ~p(/translations/#{params["id"]}/edit))
|
||||||
|
|||||||
@ -10,10 +10,18 @@ defmodule OutlookWeb.TranslationLive.Show do
|
|||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_params(%{"id" => id}, _, socket) do
|
def handle_params(%{"id" => id}, _, socket) do
|
||||||
|
translation = Translations.get_translation!(id)
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> assign(:page_title, page_title(socket.assigns.live_action))
|
|> assign(:page_title, page_title(socket.assigns.live_action))
|
||||||
|> assign(:translation, Translations.get_translation!(id))}
|
|> assign(:translation, translation)
|
||||||
|
|> assign(:translation_tree,
|
||||||
|
InternalTree.render_translation(
|
||||||
|
translation.article.content, translation.content
|
||||||
|
) |> InternalTree.garnish(
|
||||||
|
%{tunits: %{status: fn n -> n.status end, class: :tunit}}
|
||||||
|
)
|
||||||
|
)}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp page_title(:show), do: "Show Translation"
|
defp page_title(:show), do: "Show Translation"
|
||||||
|
|||||||
@ -11,15 +11,16 @@
|
|||||||
<.list>
|
<.list>
|
||||||
<:item title="Language"><%= @translation.language %></:item>
|
<:item title="Language"><%= @translation.language %></:item>
|
||||||
<:item title="Title"><%= @translation.title %></:item>
|
<:item title="Title"><%= @translation.title %></:item>
|
||||||
<:item title="Teaser"><%= @translation.teaser %></:item>
|
<:item title="Teaser"><%= @translation.teaser |> tidy_raw %></:item>
|
||||||
<%!-- <:item title="Content"><%= @translation.content %></:item> --%>
|
<%!-- <:item title="Content"><%= @translation.content %></:item> --%>
|
||||||
<:item title="Date"><%= @translation.date %></:item>
|
<:item title="Date"><%= @translation.date %></:item>
|
||||||
<:item title="Public"><%= @translation.public %></:item>
|
<:item title="Public"><%= @translation.public %></:item>
|
||||||
<:item title="Unauthorized"><%= @translation.unauthorized %></:item>
|
<:item title="Unauthorized"><%= @translation.unauthorized %></:item>
|
||||||
|
<:item title="Remarks"><%= @translation.remarks |> tidy_raw %></:item>
|
||||||
</.list>
|
</.list>
|
||||||
|
|
||||||
<div class="article">
|
<div class="article show_status">
|
||||||
<.render_doc tree={InternalTree.render_translation(@translation.article.content, @translation.content)} />
|
<.render_doc tree={@translation_tree} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<.back navigate={~p"/articles/#{@translation.article}"}>Back to <article></article></.back>
|
<.back navigate={~p"/articles/#{@translation.article}"}>Back to <article></article></.back>
|
||||||
|
|||||||
@ -12,4 +12,19 @@ defmodule OutlookWeb.ViewHelpers do
|
|||||||
def tidy_raw(whatever) do
|
def tidy_raw(whatever) do
|
||||||
whatever
|
whatever
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: implement (and use) the following function
|
||||||
|
@doc "Strip <a> tags to prevent broken html (or 'breaking') from user input."
|
||||||
|
def strip_links(html) do
|
||||||
|
raise "Yet to be implemented!"
|
||||||
|
end
|
||||||
|
|
||||||
|
def elipsed_text(text, length) do
|
||||||
|
if String.length(text) < length do
|
||||||
|
text
|
||||||
|
else
|
||||||
|
part_length = (length - 3) / 2 |> trunc()
|
||||||
|
"#{String.slice(text, 0..part_length)} … #{String.slice(text, -part_length..-1)}"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
defmodule Outlook.Repo.Migrations.AddRemarksToTranslations do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:translations) do
|
||||||
|
add :remarks, :text
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
defmodule Outlook.Repo.Migrations.AddRemarksToArticles do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:articles) do
|
||||||
|
add :remarks, :text
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
83
test/outlook/internaltree_tunitmodifications_test.exs
Normal file
83
test/outlook/internaltree_tunitmodifications_test.exs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
defmodule Outlook.InternalTreeTest do
|
||||||
|
use Outlook.DataCase
|
||||||
|
|
||||||
|
# import Outlook.InternalTreeTestHelpers
|
||||||
|
|
||||||
|
describe "internal_tree" do
|
||||||
|
alias Outlook.InternalTree
|
||||||
|
alias Outlook.InternalTree.{InternalNode,TranslationUnit}
|
||||||
|
|
||||||
|
def tree() do
|
||||||
|
[
|
||||||
|
%InternalNode{
|
||||||
|
name: "p",
|
||||||
|
attributes: %{},
|
||||||
|
type: :element,
|
||||||
|
nid: "rRIib2h8tyix",
|
||||||
|
content: [
|
||||||
|
%TranslationUnit{status: :untranslated, nid: "GuU9v6xeSS7e", content: "Joe Biden a.", eph: %{}},
|
||||||
|
%TranslationUnit{status: :untranslated, nid: "bzCLsYGNe2PG", content: "k.", eph: %{}},
|
||||||
|
%TranslationUnit{status: :untranslated, nid: "GyRUrzwH9LcP", content: "a. ", eph: %{}},
|
||||||
|
%TranslationUnit{status: :untranslated, nid: "y2yb38U4hkya", content: "Crash Test Dummy.", eph: %{}}
|
||||||
|
],
|
||||||
|
eph: %{sibling_with: :block}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "unite_with_next unites with next in simple case" do
|
||||||
|
assert InternalTree.modify_tunits(tree(), "unite_with_next", ["bzCLsYGNe2PG"]) == [
|
||||||
|
%InternalNode{
|
||||||
|
name: "p",
|
||||||
|
attributes: %{},
|
||||||
|
type: :element,
|
||||||
|
nid: "rRIib2h8tyix",
|
||||||
|
content: [
|
||||||
|
%TranslationUnit{status: :untranslated, nid: "GuU9v6xeSS7e", content: "Joe Biden a.", eph: %{}},
|
||||||
|
%TranslationUnit{status: :untranslated, nid: "bzCLsYGNe2PG", content: "k.a. ", eph: %{}},
|
||||||
|
%TranslationUnit{status: :untranslated, nid: "y2yb38U4hkya", content: "Crash Test Dummy.", eph: %{}}
|
||||||
|
],
|
||||||
|
eph: %{sibling_with: :block}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "unite_with_next unites all with next in complex case" do
|
||||||
|
assert InternalTree.modify_tunits(tree(), "unite_with_next", ["GuU9v6xeSS7e","bzCLsYGNe2PG","GyRUrzwH9LcP"]) == [
|
||||||
|
%InternalNode{
|
||||||
|
name: "p",
|
||||||
|
attributes: %{},
|
||||||
|
type: :element,
|
||||||
|
nid: "rRIib2h8tyix",
|
||||||
|
content: [
|
||||||
|
%TranslationUnit{
|
||||||
|
status: :untranslated,
|
||||||
|
nid: "GuU9v6xeSS7e",
|
||||||
|
content: "Joe Biden a.k.a. Crash Test Dummy.",
|
||||||
|
eph: %{}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
eph: %{sibling_with: :block}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "unite_with_next ignores id if there is no next tunit" do
|
||||||
|
assert InternalTree.modify_tunits(tree(), "unite_with_next", ["y2yb38U4hkya"]) == [
|
||||||
|
%InternalNode{
|
||||||
|
name: "p",
|
||||||
|
attributes: %{},
|
||||||
|
type: :element,
|
||||||
|
nid: "rRIib2h8tyix",
|
||||||
|
content: [
|
||||||
|
%TranslationUnit{status: :untranslated, nid: "GuU9v6xeSS7e", content: "Joe Biden a.", eph: %{}},
|
||||||
|
%TranslationUnit{status: :untranslated, nid: "bzCLsYGNe2PG", content: "k.", eph: %{}},
|
||||||
|
%TranslationUnit{status: :untranslated, nid: "GyRUrzwH9LcP", content: "a. ", eph: %{}},
|
||||||
|
%TranslationUnit{status: :untranslated, nid: "y2yb38U4hkya", content: "Crash Test Dummy.", eph: %{}}
|
||||||
|
],
|
||||||
|
eph: %{sibling_with: :block}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
131
test/outlook/public_test.exs
Normal file
131
test/outlook/public_test.exs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
defmodule Outlook.PublicTest do
|
||||||
|
use Outlook.DataCase
|
||||||
|
|
||||||
|
# TODO: make this work
|
||||||
|
|
||||||
|
alias Outlook.Public
|
||||||
|
|
||||||
|
describe "artikel" do
|
||||||
|
alias Outlook.Public.Artikel
|
||||||
|
|
||||||
|
import Outlook.PublicFixtures
|
||||||
|
|
||||||
|
@invalid_attrs %{date: nil, public_content: nil, teaser: nil, title: nil, translator: nil, unauthorized: nil}
|
||||||
|
|
||||||
|
test "list_artikel/0 returns all artikel" do
|
||||||
|
artikel = artikel_fixture()
|
||||||
|
assert Artikel.list_artikel() == [artikel]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_artikel!/1 returns the artikel with given id" do
|
||||||
|
artikel = artikel_fixture()
|
||||||
|
assert Artikel.get_artikel!(artikel.id) == artikel
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create_artikel/1 with valid data creates a artikel" do
|
||||||
|
valid_attrs = %{date: "some date", public_content: "some public_content", teaser: "some teaser", title: "some title", translator: "some translator", unauthorized: true}
|
||||||
|
|
||||||
|
assert {:ok, %Artikel{} = artikel} = Artikel.create_artikel(valid_attrs)
|
||||||
|
assert artikel.date == "some date"
|
||||||
|
assert artikel.public_content == "some public_content"
|
||||||
|
assert artikel.teaser == "some teaser"
|
||||||
|
assert artikel.title == "some title"
|
||||||
|
assert artikel.translator == "some translator"
|
||||||
|
assert artikel.unauthorized == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create_artikel/1 with invalid data returns error changeset" do
|
||||||
|
assert {:error, %Ecto.Changeset{}} = Artikel.create_artikel(@invalid_attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update_artikel/2 with valid data updates the artikel" do
|
||||||
|
artikel = artikel_fixture()
|
||||||
|
update_attrs = %{date: "some updated date", public_content: "some updated public_content", teaser: "some updated teaser", title: "some updated title", translator: "some updated translator", unauthorized: false}
|
||||||
|
|
||||||
|
assert {:ok, %Artikel{} = artikel} = Artikel.update_artikel(artikel, update_attrs)
|
||||||
|
assert artikel.date == "some updated date"
|
||||||
|
assert artikel.public_content == "some updated public_content"
|
||||||
|
assert artikel.teaser == "some updated teaser"
|
||||||
|
assert artikel.title == "some updated title"
|
||||||
|
assert artikel.translator == "some updated translator"
|
||||||
|
assert artikel.unauthorized == false
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update_artikel/2 with invalid data returns error changeset" do
|
||||||
|
artikel = artikel_fixture()
|
||||||
|
assert {:error, %Ecto.Changeset{}} = Artikel.update_artikel(artikel, @invalid_attrs)
|
||||||
|
assert artikel == Artikel.get_artikel!(artikel.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "delete_artikel/1 deletes the artikel" do
|
||||||
|
artikel = artikel_fixture()
|
||||||
|
assert {:ok, %Artikel{}} = Artikel.delete_artikel(artikel)
|
||||||
|
assert_raise Ecto.NoResultsError, fn -> Artikel.get_artikel!(artikel.id) end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "change_artikel/1 returns a artikel changeset" do
|
||||||
|
artikel = artikel_fixture()
|
||||||
|
assert %Ecto.Changeset{} = Artikel.change_artikel(artikel)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "autoren" do
|
||||||
|
alias Outlook.Public.Autor
|
||||||
|
|
||||||
|
import Outlook.PublicFixtures
|
||||||
|
|
||||||
|
@invalid_attrs %{description: nil, homepage_name: nil, homepage_url: nil, name: nil}
|
||||||
|
|
||||||
|
test "list_autoren/0 returns all autoren" do
|
||||||
|
autor = autor_fixture()
|
||||||
|
assert Autoren.list_autoren() == [autor]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_autor!/1 returns the autor with given id" do
|
||||||
|
autor = autor_fixture()
|
||||||
|
assert Autoren.get_autor!(autor.id) == autor
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create_autor/1 with valid data creates a autor" do
|
||||||
|
valid_attrs = %{description: "some description", homepage_name: "some homepage_name", homepage_url: "some homepage_url", name: "some name"}
|
||||||
|
|
||||||
|
assert {:ok, %Autor{} = autor} = Autoren.create_autor(valid_attrs)
|
||||||
|
assert autor.description == "some description"
|
||||||
|
assert autor.homepage_name == "some homepage_name"
|
||||||
|
assert autor.homepage_url == "some homepage_url"
|
||||||
|
assert autor.name == "some name"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create_autor/1 with invalid data returns error changeset" do
|
||||||
|
assert {:error, %Ecto.Changeset{}} = Autoren.create_autor(@invalid_attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update_autor/2 with valid data updates the autor" do
|
||||||
|
autor = autor_fixture()
|
||||||
|
update_attrs = %{description: "some updated description", homepage_name: "some updated homepage_name", homepage_url: "some updated homepage_url", name: "some updated name"}
|
||||||
|
|
||||||
|
assert {:ok, %Autor{} = autor} = Autoren.update_autor(autor, update_attrs)
|
||||||
|
assert autor.description == "some updated description"
|
||||||
|
assert autor.homepage_name == "some updated homepage_name"
|
||||||
|
assert autor.homepage_url == "some updated homepage_url"
|
||||||
|
assert autor.name == "some updated name"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update_autor/2 with invalid data returns error changeset" do
|
||||||
|
autor = autor_fixture()
|
||||||
|
assert {:error, %Ecto.Changeset{}} = Autoren.update_autor(autor, @invalid_attrs)
|
||||||
|
assert autor == Autoren.get_autor!(autor.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "delete_autor/1 deletes the autor" do
|
||||||
|
autor = autor_fixture()
|
||||||
|
assert {:ok, %Autor{}} = Autoren.delete_autor(autor)
|
||||||
|
assert_raise Ecto.NoResultsError, fn -> Autoren.get_autor!(autor.id) end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "change_autor/1 returns a autor changeset" do
|
||||||
|
autor = autor_fixture()
|
||||||
|
assert %Ecto.Changeset{} = Autoren.change_autor(autor)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
86
test/outlook_web/controllers/artikel_controller_test.exs
Normal file
86
test/outlook_web/controllers/artikel_controller_test.exs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
defmodule OutlookWeb.ArtikelControllerTest do
|
||||||
|
use OutlookWeb.ConnCase
|
||||||
|
|
||||||
|
# TODO: make this work
|
||||||
|
|
||||||
|
import Outlook.PublicFixtures
|
||||||
|
|
||||||
|
@create_attrs %{date: "some date", public_content: "some public_content", teaser: "some teaser", title: "some title", translator: "some translator", unauthorized: true}
|
||||||
|
@update_attrs %{date: "some updated date", public_content: "some updated public_content", teaser: "some updated teaser", title: "some updated title", translator: "some updated translator", unauthorized: false}
|
||||||
|
@invalid_attrs %{date: nil, public_content: nil, teaser: nil, title: nil, translator: nil, unauthorized: nil}
|
||||||
|
|
||||||
|
describe "index" do
|
||||||
|
test "lists all artikel", %{conn: conn} do
|
||||||
|
conn = get(conn, ~p"/artikel")
|
||||||
|
assert html_response(conn, 200) =~ "Listing Artikel"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "new artikel" do
|
||||||
|
test "renders form", %{conn: conn} do
|
||||||
|
conn = get(conn, ~p"/artikel/new")
|
||||||
|
assert html_response(conn, 200) =~ "New Artikel"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "create artikel" do
|
||||||
|
test "redirects to show when data is valid", %{conn: conn} do
|
||||||
|
conn = post(conn, ~p"/artikel", artikel: @create_attrs)
|
||||||
|
|
||||||
|
assert %{id: id} = redirected_params(conn)
|
||||||
|
assert redirected_to(conn) == ~p"/artikel/#{id}"
|
||||||
|
|
||||||
|
conn = get(conn, ~p"/artikel/#{id}")
|
||||||
|
assert html_response(conn, 200) =~ "Artikel #{id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders errors when data is invalid", %{conn: conn} do
|
||||||
|
conn = post(conn, ~p"/artikel", artikel: @invalid_attrs)
|
||||||
|
assert html_response(conn, 200) =~ "New Artikel"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "edit artikel" do
|
||||||
|
setup [:create_artikel]
|
||||||
|
|
||||||
|
test "renders form for editing chosen artikel", %{conn: conn, artikel: artikel} do
|
||||||
|
conn = get(conn, ~p"/artikel/#{artikel}/edit")
|
||||||
|
assert html_response(conn, 200) =~ "Edit Artikel"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "update artikel" do
|
||||||
|
setup [:create_artikel]
|
||||||
|
|
||||||
|
test "redirects when data is valid", %{conn: conn, artikel: artikel} do
|
||||||
|
conn = put(conn, ~p"/artikel/#{artikel}", artikel: @update_attrs)
|
||||||
|
assert redirected_to(conn) == ~p"/artikel/#{artikel}"
|
||||||
|
|
||||||
|
conn = get(conn, ~p"/artikel/#{artikel}")
|
||||||
|
assert html_response(conn, 200) =~ "some updated date"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders errors when data is invalid", %{conn: conn, artikel: artikel} do
|
||||||
|
conn = put(conn, ~p"/artikel/#{artikel}", artikel: @invalid_attrs)
|
||||||
|
assert html_response(conn, 200) =~ "Edit Artikel"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "delete artikel" do
|
||||||
|
setup [:create_artikel]
|
||||||
|
|
||||||
|
test "deletes chosen artikel", %{conn: conn, artikel: artikel} do
|
||||||
|
conn = delete(conn, ~p"/artikel/#{artikel}")
|
||||||
|
assert redirected_to(conn) == ~p"/artikel"
|
||||||
|
|
||||||
|
assert_error_sent 404, fn ->
|
||||||
|
get(conn, ~p"/artikel/#{artikel}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_artikel(_) do
|
||||||
|
artikel = artikel_fixture()
|
||||||
|
%{artikel: artikel}
|
||||||
|
end
|
||||||
|
end
|
||||||
86
test/outlook_web/controllers/autor_controller_test.exs
Normal file
86
test/outlook_web/controllers/autor_controller_test.exs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
defmodule OutlookWeb.AutorControllerTest do
|
||||||
|
use OutlookWeb.ConnCase
|
||||||
|
|
||||||
|
# TODO: make this work
|
||||||
|
|
||||||
|
import Outlook.PublicFixtures
|
||||||
|
|
||||||
|
@create_attrs %{description: "some description", homepage_name: "some homepage_name", homepage_url: "some homepage_url", name: "some name"}
|
||||||
|
@update_attrs %{description: "some updated description", homepage_name: "some updated homepage_name", homepage_url: "some updated homepage_url", name: "some updated name"}
|
||||||
|
@invalid_attrs %{description: nil, homepage_name: nil, homepage_url: nil, name: nil}
|
||||||
|
|
||||||
|
describe "index" do
|
||||||
|
test "lists all autoren", %{conn: conn} do
|
||||||
|
conn = get(conn, ~p"/autoren")
|
||||||
|
assert html_response(conn, 200) =~ "Listing Autoren"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "new autor" do
|
||||||
|
test "renders form", %{conn: conn} do
|
||||||
|
conn = get(conn, ~p"/autoren/new")
|
||||||
|
assert html_response(conn, 200) =~ "New Autor"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "create autor" do
|
||||||
|
test "redirects to show when data is valid", %{conn: conn} do
|
||||||
|
conn = post(conn, ~p"/autoren", autor: @create_attrs)
|
||||||
|
|
||||||
|
assert %{id: id} = redirected_params(conn)
|
||||||
|
assert redirected_to(conn) == ~p"/autoren/#{id}"
|
||||||
|
|
||||||
|
conn = get(conn, ~p"/autoren/#{id}")
|
||||||
|
assert html_response(conn, 200) =~ "Autor #{id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders errors when data is invalid", %{conn: conn} do
|
||||||
|
conn = post(conn, ~p"/autoren", autor: @invalid_attrs)
|
||||||
|
assert html_response(conn, 200) =~ "New Autor"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "edit autor" do
|
||||||
|
setup [:create_autor]
|
||||||
|
|
||||||
|
test "renders form for editing chosen autor", %{conn: conn, autor: autor} do
|
||||||
|
conn = get(conn, ~p"/autoren/#{autor}/edit")
|
||||||
|
assert html_response(conn, 200) =~ "Edit Autor"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "update autor" do
|
||||||
|
setup [:create_autor]
|
||||||
|
|
||||||
|
test "redirects when data is valid", %{conn: conn, autor: autor} do
|
||||||
|
conn = put(conn, ~p"/autoren/#{autor}", autor: @update_attrs)
|
||||||
|
assert redirected_to(conn) == ~p"/autoren/#{autor}"
|
||||||
|
|
||||||
|
conn = get(conn, ~p"/autoren/#{autor}")
|
||||||
|
assert html_response(conn, 200) =~ "some updated description"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders errors when data is invalid", %{conn: conn, autor: autor} do
|
||||||
|
conn = put(conn, ~p"/autoren/#{autor}", autor: @invalid_attrs)
|
||||||
|
assert html_response(conn, 200) =~ "Edit Autor"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "delete autor" do
|
||||||
|
setup [:create_autor]
|
||||||
|
|
||||||
|
test "deletes chosen autor", %{conn: conn, autor: autor} do
|
||||||
|
conn = delete(conn, ~p"/autoren/#{autor}")
|
||||||
|
assert redirected_to(conn) == ~p"/autoren"
|
||||||
|
|
||||||
|
assert_error_sent 404, fn ->
|
||||||
|
get(conn, ~p"/autoren/#{autor}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_autor(_) do
|
||||||
|
autor = autor_fixture()
|
||||||
|
%{autor: autor}
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -4,6 +4,8 @@ defmodule Outlook.ArticlesFixtures do
|
|||||||
entities via the `Outlook.Articles` context.
|
entities via the `Outlook.Articles` context.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# TODO: make this work
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Generate a article.
|
Generate a article.
|
||||||
"""
|
"""
|
||||||
@ -11,11 +13,17 @@ defmodule Outlook.ArticlesFixtures do
|
|||||||
{:ok, article} =
|
{:ok, article} =
|
||||||
attrs
|
attrs
|
||||||
|> Enum.into(%{
|
|> Enum.into(%{
|
||||||
content: "some content",
|
content: [%Outlook.InternalTree.InternalNode{name: "p", attributes: %{}, type: :element, nid: "54e8cedb-6459-4605-8301-367758675bb8", content: [
|
||||||
|
%Outlook.InternalTree.TranslationUnit{status: :untranslated, nid: "c0fcdf61-ae2d-482e-81b4-9b6e3baacd8b",
|
||||||
|
content: "A sentence with many letters <a href=\"dingsda.com\">and many, many <b>words. </b></a>"},
|
||||||
|
%Outlook.InternalTree.TranslationUnit{status: :untranslated, nid: "eac12d97-623d-4237-9f33-666298c7f494",
|
||||||
|
content: "<a href=\"dingsda.com\"><b>A</b> sentence</a> with many letters and many, many words. "}],
|
||||||
|
eph: %{sibling_with: :block}}],
|
||||||
date: ~U[2022-12-25 16:16:00Z],
|
date: ~U[2022-12-25 16:16:00Z],
|
||||||
language: "some language",
|
language: "EN",
|
||||||
title: "some title",
|
title: "some title",
|
||||||
url: "some url"
|
url: "some url",
|
||||||
|
author_id: 1
|
||||||
})
|
})
|
||||||
|> Outlook.Articles.create_article()
|
|> Outlook.Articles.create_article()
|
||||||
|
|
||||||
|
|||||||
45
test/support/fixtures/public_fixtures.ex
Normal file
45
test/support/fixtures/public_fixtures.ex
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
defmodule Outlook.PublicFixtures do
|
||||||
|
@moduledoc """
|
||||||
|
This module defines test helpers for creating
|
||||||
|
entities via the `Outlook.Public` context.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# TODO: make this work
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Generate an artikel.
|
||||||
|
"""
|
||||||
|
def artikel_fixture(attrs \\ %{}) do
|
||||||
|
{:ok, artikel} =
|
||||||
|
attrs
|
||||||
|
|> Enum.into(%{
|
||||||
|
date: "some date",
|
||||||
|
public_content: "some public_content",
|
||||||
|
teaser: "some teaser",
|
||||||
|
title: "some title",
|
||||||
|
translator: "some translator",
|
||||||
|
unauthorized: true
|
||||||
|
})
|
||||||
|
|> Outlook.Public.create_artikel()
|
||||||
|
|
||||||
|
artikel
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Generate an autor.
|
||||||
|
"""
|
||||||
|
def autor_fixture(attrs \\ %{}) do
|
||||||
|
{:ok, autor} =
|
||||||
|
attrs
|
||||||
|
|> Enum.into(%{
|
||||||
|
description: "some description",
|
||||||
|
homepage_name: "some homepage_name",
|
||||||
|
homepage_url: "some homepage_url",
|
||||||
|
name: "some name"
|
||||||
|
})
|
||||||
|
|> Outlook.Public.create_autor()
|
||||||
|
|
||||||
|
autor
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@ -4,6 +4,8 @@ defmodule Outlook.TranslationsFixtures do
|
|||||||
entities via the `Outlook.Translations` context.
|
entities via the `Outlook.Translations` context.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# TODO: make this work
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Generate a translation.
|
Generate a translation.
|
||||||
"""
|
"""
|
||||||
@ -17,7 +19,8 @@ defmodule Outlook.TranslationsFixtures do
|
|||||||
public: true,
|
public: true,
|
||||||
teaser: "some teaser",
|
teaser: "some teaser",
|
||||||
title: "some title",
|
title: "some title",
|
||||||
unauthorized: true
|
unauthorized: true,
|
||||||
|
article_id: 1
|
||||||
})
|
})
|
||||||
|> Outlook.Translations.create_translation()
|
|> Outlook.Translations.create_translation()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user