Compare commits

47 Commits

Author SHA1 Message Date
2111cfa1d1 Add iframe to elements that may be empty 2025-05-05 14:45:43 +02:00
c8d1639afa Fix ugly bug 2023-06-09 13:57:05 +02:00
43e11178db Fix darkmode-widget cover buttons 2023-06-08 16:33:03 +02:00
4828a68bcd Fix following links when editing translation 2023-05-29 13:19:42 +02:00
2b25c13095 Fix bug which omits element's attributes
There is a questionable design decision: for
getting the attributes from the %InternalNode{} into
the rendered html output they always have to be copied
into node.eph.attributes. (OutlookWeb.HtmlDocComponent.dnode())
2023-05-28 15:31:08 +02:00
357bcae450 Add ’ to partitioning regex 2023-05-28 15:23:25 +02:00
698da7f8b7 Add toggling fields in translation form 2023-05-04 18:14:44 +02:00
d53eceb7a0 Add remarks to articles 2023-05-04 18:12:57 +02:00
888e7575f0 Add remarks field to translations 2023-05-04 18:11:40 +02:00
8a0e2f22c1 Improve html tree component 2023-05-03 22:37:43 +02:00
7990b74bf0 Add "Unite with next" button 2023-05-02 22:09:04 +02:00
4a8f1c3c80 Update add_phx_click_event making :target option optional 2023-05-02 21:59:27 +02:00
7e085f5202 Update layout 2023-03-30 14:31:10 +02:00
3b8b1b19f0 Cleanup TunitModifications 2023-03-28 23:44:19 +02:00
5ac1a6c8e7 Remove superfluous line 2023-03-28 14:51:57 +02:00
3a2af2adb4 Add TunitModifications with first modifier unite_with_next 2023-03-28 14:48:26 +02:00
b92503d643 Replace outdated render_html_preview() with render_doc component 2023-03-15 13:24:54 +01:00
2eba3bc500 Update Translators context 2023-03-15 11:26:07 +01:00
43f3ea193f Add miscellaneous stuff 2023-03-15 10:56:30 +01:00
61253f301a Add mainly disfunctional test
Due to utter negligence test results are currently:

221 tests, 186 failures
2023-03-15 10:49:29 +01:00
5d43e61223 Update status styles 2023-03-08 21:09:12 +01:00
724d161f50 Add status preview in translation_live/show 2023-03-08 20:43:42 +01:00
1fb9a40f2c Add overlooked ids for buttons 2023-03-06 23:01:31 +01:00
459c8e6a57 Add shortcuts for saving 2023-03-05 23:13:58 +01:00
21b97bec6e Sanitize variable name 2023-03-05 22:53:52 +01:00
5d9238a65a Fix ugly bug and add more shortcuts 2023-03-05 22:05:54 +01:00
e20f8e33ee Add selecting next/previous tunit and highlight it 2023-03-05 20:56:45 +01:00
4e6c516cb6 Add Public context to iex-local.exs 2023-03-05 20:42:51 +01:00
bacb61252f Adapt core_components table() to dark mode 2023-03-04 23:46:39 +01:00
8a513b1452 Remove unused code 2023-03-04 23:45:49 +01:00
895860baa6 Update Public.get_autor! to only display public Artikel
Also get rid of the superfluous additional loop over articles/translations.
2023-03-03 22:05:52 +01:00
5319785855 Update continue_edit() to reload Translation
Reloading is necessary to detect changes in the changeset. Otherwise
simple changes like changing the value of translation.public wouldn't
get noticed and not being saved to db.
2023-03-03 21:57:09 +01:00
b20bbb232c Refactor query functions 2023-03-02 23:17:21 +01:00
cbea9450e4 Add Autor schema 2023-03-02 23:13:32 +01:00
3fe4a331ac Remove detour over Enum.into(%{}) for keyword results to %Artikel{} 2023-03-01 16:01:14 +01:00
6d0ae825d8 Remove tids for Translations 2023-03-01 15:58:38 +01:00
3a2769eed1 Refactor Autoren context into Public context 2023-03-01 15:24:36 +01:00
e8089eb24e Add important amendment
Yesterday night it got late...
2023-03-01 12:41:09 +01:00
02e6340c0a Add embedded schema for Artikel 2023-02-28 23:47:37 +01:00
ed98f4cbc4 Add some styling 2023-02-28 21:45:04 +01:00
b87b54ec71 Refactor get_artikel*() functions/sql 2023-02-28 21:42:05 +01:00
fc0818678c Add dark mode styling to form elements 2023-02-28 21:38:11 +01:00
dc4c8e4790 Update tidy_raw() to ignore null values 2023-02-28 21:35:09 +01:00
e98187200a Update to Phoenix 1.7.0 final release 2023-02-27 19:47:13 +01:00
b47d7d081d Add .iex-local.exs 2023-02-23 15:46:49 +01:00
2ffea3e490 Add rest of other commit (appr. HEAD~20?) 2023-02-22 14:24:49 +01:00
cb8e9ef14f Add another tidy_raw() 2023-02-14 22:52:43 +01:00
53 changed files with 1087 additions and 262 deletions

22
.iex-local.exs Normal file
View File

@ -0,0 +1,22 @@
alias Outlook.HtmlPreparations
alias Outlook.HtmlPreparations.HtmlPreparation
alias Outlook.InternalTree.{Html,InternalNode,TranslationUnit,TunitModifications}
alias Outlook.InternalTree
alias Outlook.Articles
alias Outlook.Accounts
alias Outlook.Articles.Article
alias Outlook.Authors
alias Outlook.Authors.Author
alias Outlook.Translations
alias Outlook.Translations.Translation
alias Outlook.Translators.{Deepl,DeeplAccount}
alias Outlook.Translators
alias Outlook.Public
alias Outlook.Public.{Artikel,Autor}
alias Outlook.Repo
import Ecto.Query, warn: false
html = """
<p class="">Das Young-Global-Leaders-Programm des WEF von Klaus Schwab tut seit Anfang der 90er Jahre das gleiche und wäre es ein Konkurrenzprodukt gegen die bestehenden, transatlantischen US-Programme, wäre das Projekt sofort abgewürgt worden. Stattdessen ist es ein großer Erfolg und sehr viele der heute weltweit führenden Politiker sind durch die Schule von Klaus Schwab gegangen und setzen als Minister und sogar Regierungschefs brav die Politik um, für die sich Schwab selbst einsetzt.</p>
<p>Die Frage ist also, wie und mit wessen Hilfe es der aus kleinen Verhältnissen stammende Klaus Schwab geschafft hat, so mächtig zu werden. Die Antwort ist verblüffend einfach: Er hat als Student selbst so ein Programm durchlaufen. Damals war es noch die CIA, die relativ offen hinter diesem Programm stand und junge Leute gesucht hat, deren Karriere die CIA gefördert hat, damit diese Leute später das umsetzen, was von der CIA gewollt ist. Inzwischen hat Schwab mit seinem WEF diese Funktion übernommen und sein Young-Global-Leaders-Programm ist nichts anderes, als das Nachfolgeprogramm eines CIA-Programms aus den 1950er Jahren.</p>
<p>Auf den Artikel bin ich durch einen Hinweis eines Lesers auf einen <a rel="noreferrer noopener" href="https://tkp.at/2022/09/02/das-young-global-leaders-programm-des-wef-und-sein-ursprung-in-den-usa/" target="_blank">Artikel bei tkp </a>gestoßen, der den englischen Artikel zusammengefasst hat. Da die Informationen im Original so spannend und die Details zum Verständnis so wichtig sind, habe ich den <a rel="noreferrer noopener" href="https://unlimitedhangout.com/2022/08/investigative-reports/the-kissinger-continuum-the-unauthorized-history-of-the-wefs-young-global-leaders-program/" target="_blank" class="">englischen Originalartikel</a> übersetzt, um mich nicht mit fremden Federn zu schmücken. Die Links habe ich aus dem Originalartikel übernommen.</p>
"""

View File

@ -1,10 +1,49 @@
.main {
@apply place-content-center;
}
.article {
/* @apply pr-8 */
max-width: 25rem;
}
.article .tunit {
.article span.tunit {
@apply hover:bg-gray-300;
padding: 5px 0;
margin: -5px 0;
}
.dark .article span.tunit {
@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 {

View File

@ -23,11 +23,14 @@ import {LiveSocket} from "phoenix_live_view"
import topbar from "../vendor/topbar"
import {DarkModeHook} from './dark-mode-widget'
import {TranslationFormHook} from "./translation-form"
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {
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

View 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}

View File

@ -12,6 +12,7 @@ defmodule Outlook.Articles.Article do
field :language, :string, default: "EN"
field :title, :string
field :url, :string
field :remarks, :string
belongs_to :author, Author
has_many :translations, Translation, on_delete: :delete_all
@ -21,7 +22,7 @@ defmodule Outlook.Articles.Article do
@doc false
def changeset(article, attrs) do
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])
|> foreign_key_constraint(:author_id)
end

View File

@ -1,33 +0,0 @@
defmodule Outlook.Artikel do
@moduledoc """
The Artikel context.
"""
alias Outlook.Translations.Translation
import Ecto.Query, warn: false
alias Outlook.Repo
def list_artikel do
Repo.all(from t in Translation, where: t.public == true)
|> Repo.preload([article: :author])
end
def get_artikel!(artikel) when is_struct(artikel), do: get_artikel!(artikel.id)
def get_artikel!(id) do
Repo.one(from t in Translation, where: t.id == ^id and t.public == true)
|> Repo.preload([article: :author])
end
def get_artikel_by_tid(tid) do
artikel = tid
|> String.split(~r/--(?=[0-9A-Za-z])/)
|> List.last()
|> String.to_integer(36)
|> get_artikel!()
case artikel do
%Translation{} -> {:ok, artikel}
_ -> {:error, "Artikel does not exist, or isn't public."}
end
end
end

View File

@ -39,7 +39,7 @@ defmodule Outlook.Authors do
def get_author_with_articles!(id) do
Repo.get!(Author, id)
|> Repo.preload([:articles])
|> Repo.preload([articles: :translations])
end
@doc """

View File

@ -9,7 +9,7 @@ defmodule Outlook.Authors.Author do
field :homepage_name, :string
field :homepage_url, :string
field :name, :string
has_many :articles, Article
has_many :articles, Article, on_delete: :delete_all
timestamps()
end

View File

@ -1,34 +0,0 @@
defmodule Outlook.Autoren do
@moduledoc """
The Autoren context.
"""
import Ecto.Query, warn: false
alias Outlook.Repo
alias Outlook.Articles.Article
alias Outlook.Translations.Translation
alias Outlook.Authors.Author
def list_autoren do
Repo.all(Author)
end
def get_autor!(id) do
Repo.get!(Author, id)
|> Repo.preload([articles: [:translations]])
end
@doc "This is ugly"
def list_artikel(author) when is_struct(author), do: list_artikel(author.id)
def list_artikel(author_id) do
aids = Repo.all(from a in Article,
select: [:id],
where: a.author_id == ^author_id)
|> Enum.map(fn a -> a.id end)
Repo.all(from t in Translation,
select: [t.title, t.teaser, t.date, t.user_id],
where: t.article_id in ^aids and t.public == true)
end
end

View File

@ -1,6 +1,6 @@
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.{Hyphenation, Translations}
@ -10,11 +10,6 @@ defmodule Outlook.InternalTree do
|> Html.to_html()
end
def render_html_preview(tree, target \\ "1") do
tree
|> Html.to_html_preview(target)
end
require Logger
def apply_modifier(tree, modifier, nids, opts \\ %{}) do
# Logger.info modifier
@ -31,9 +26,12 @@ defmodule Outlook.InternalTree do
def add_phx_click_event(tree, opts) do
phx_opts = %{
"phx-click": Keyword.get(opts, :click),
"phx-target": Keyword.get(opts, :target) |> to_string,
"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)
garnish(tree, options)
end
@ -58,6 +56,7 @@ defmodule Outlook.InternalTree do
def render_public_content(tree, translation, language) do
Translation.render_translation(tree, translation)
|> garnish(%{})
|> Html.render_doc()
|> Hyphenation.hyphenate(language)
end
@ -74,6 +73,13 @@ defmodule Outlook.InternalTree do
def get_tunit_ids(tree) do
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

View File

@ -51,30 +51,6 @@ defmodule Outlook.InternalTree.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
OutlookWeb.HtmlDocComponent.render_doc(%{tree: tree})

View File

@ -58,4 +58,19 @@ defmodule Outlook.InternalTree.InternalTree do
|> Enum.into(node_atts)
%{node | eph: Map.put(node.eph, :attributes, attributes)}
end
def collect_tunit_ids([%TranslationUnit{} = node | rest]) do
[node.nid | collect_tunit_ids(rest)]
end
def collect_tunit_ids([%{type: :element} = node | rest]) do
collect_tunit_ids(node.content) ++ collect_tunit_ids(rest)
end
def collect_tunit_ids([node | rest]) do
collect_tunit_ids(rest)
end
def collect_tunit_ids([]), do: []
end

View File

@ -10,7 +10,7 @@ defmodule Outlook.InternalTree.RawInternalBasic do
@splitmarker "@@translationunit@@"
@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
[ %InternalNode{textnode |
@ -18,7 +18,7 @@ defmodule Outlook.InternalTree.RawInternalBasic do
|> String.replace(~r/\.\.\.+/u, "")
|> String.replace(~r/([[:upper:]])\./u, "\\1#{@nonperiodmarker}")
|> 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, ".")
} | set_split_markers(rest) ]
end

View 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

94
lib/outlook/public.ex Normal file
View File

@ -0,0 +1,94 @@
defmodule Outlook.Public do
@moduledoc """
This should replace Outlook.Artikel and Outlook.Autoren for
both of which embedded schemas should be created, for Artikel the schema should
implement to_param protocol (no more needed for Outlook.Translations.Translation then).
"""
alias Outlook.Translations.Translation
alias Outlook.Articles.Article
alias Outlook.Authors.Author
alias Outlook.Public.{Artikel,Autor}
import Ecto.Query, warn: false
alias Outlook.Repo
def list_artikel(language \\ "DE") do
q = from t in Translation,
join: a in Article, on: t.article_id == a.id,
join: au in Author, on: a.author_id == au.id,
select: %Artikel{
title: t.title,
date: t.date,
teaser: t.teaser,
id: t.id,
date_org: a.date,
autor_name: au.name,
},
where: t.public == true and t.language == ^language,
order_by: [desc: t.date]
Repo.all(q)
end
def get_artikel!(artikel) when is_struct(artikel), do: get_artikel!(artikel.id)
def get_artikel!(id) do
q = from t in Translation,
join: a in Article, on: t.article_id == a.id,
join: au in Author, on: a.author_id == au.id,
select: %Artikel{
title: t.title,
date: t.date,
public_content: t.public_content,
title_org: a.title,
url_org: a.url,
date_org: a.date,
autor_name: au.name,
autor_id: au.id
},
where: t.id == ^id and t.public == true
case Repo.one(q) do
nil -> {:error, "Artikel does not exist, or isn't public."}
artikel -> {:ok, artikel}
end
end
def get_artikel_by_tid(tid) do
tid
|> String.split(~r/--(?=[0-9A-Za-z])/)
|> List.last()
|> String.to_integer(36)
|> get_artikel!()
end
# for /autoren/
def list_autoren do
Repo.all(Author)
end
def get_autor!(id) do
q = from au in Author,
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

View File

@ -0,0 +1,43 @@
defmodule Outlook.Public.Artikel do
use Ecto.Schema
alias Outlook.Public.Artikel
embedded_schema do
field :title, :string
field :date, :utc_datetime
field :public_content, :string
field :title_org, :string
field :url_org, :string
field :date_org, :utc_datetime
field :autor_name, :string
field :autor_id, :integer
field :teaser, :string
# field :autor, Autor
end
def translate_unicode(str) do
mapping = %{"Ä" => "Ae",
"Ö" => "Oe",
"Ü" => "Ue",
"ä" => "ae",
"ö" => "oe",
"ü" => "ue",
"ß" => "ss"}
{:ok, re} = "[#{Map.keys(mapping) |> Enum.join}]" |> Regex.compile("u")
Regex.replace(re, str, fn(c) -> mapping[c] end)
end
def spit_title(title) do
title
|> translate_unicode()
|> String.replace(~r/[^\w\s-]/u, "")
|> String.replace(~r/(\s|-)+/u, "-")
end
defimpl Phoenix.Param, for: Artikel do
def to_param(%{id: id, title: title}) do
"#{Artikel.spit_title(title)}--#{Integer.to_string(id, 36) |> String.downcase()}"
end
end
end

View 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

View File

@ -4,7 +4,7 @@ defmodule Outlook.Translations.Translation do
alias Outlook.Accounts.User
alias Outlook.Articles.Article
alias Outlook.Translations.{TranslationUnitsMap,Translation}
alias Outlook.Translations.TranslationUnitsMap
schema "translations" do
field :content, TranslationUnitsMap
@ -15,6 +15,7 @@ defmodule Outlook.Translations.Translation do
field :public_content, :string
field :title, :string
field :unauthorized, :boolean, default: false
field :remarks, :string
belongs_to :user, User
belongs_to :article, Article
@ -24,7 +25,7 @@ defmodule Outlook.Translations.Translation do
@doc false
def changeset(translation, attrs) do
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])
|> validate_required([:language, :title, :content, :date, :public, :unauthorized, :article_id])
|> unique_constraint([:language, :article_id],
@ -32,30 +33,4 @@ defmodule Outlook.Translations.Translation do
name: :article_id_lang_unique_index)
|> foreign_key_constraint(:article_id)
end
def translate_unicode(str) do
mapping = %{"Ä" => "Ae",
"Ö" => "Oe",
"Ü" => "Ue",
"ä" => "ae",
"ö" => "oe",
"ü" => "ue",
"ß" => "ss"}
{:ok, re} = "[#{Map.keys(mapping) |> Enum.join}]" |> Regex.compile("u")
Regex.replace(re, str, fn(c) -> mapping[c] end)
end
def spit_title(title) do
title
|> translate_unicode()
# |> String.replace(~r/[^-0-9A-Za-z ]/u, "_")
|> String.replace(~r/[^\w\s-]/u, "")
|> String.replace(~r/(\s|-)+/u, "-")
end
defimpl Phoenix.Param, for: Translation do
def to_param(%{id: id, title: title}) do
"#{Translation.spit_title(title)}--#{Integer.to_string(id, 36) |> String.downcase()}"
end
end
end

View File

@ -83,7 +83,7 @@ defmodule Outlook.Translators do
|> IO.iodata_to_binary()
end
def process_translation(translation, tunit_ids) do
defp process_translation(translation, tunit_ids) do
tunit_map = translation
|> Floki.parse_fragment!
|> Floki.find("tunit")

View File

@ -54,10 +54,12 @@ defmodule Outlook.Translators.Deepl do
)
response = Jason.decode!(response_raw.body, keys: :atoms)
require Logger
case response do
%{status: "done"} ->
response
%{status: status} ->
Logger.debug "Deepl response: #{response |> inspect}"
steps = Map.get(response, :seconds_remaining, 1) * 5
for n <- 0..steps do
send(pid, {:progress, %{progress: 100 * n / steps, status: status}})

View File

@ -191,7 +191,7 @@ defmodule OutlookWeb.CoreComponents do
def simple_form(assigns) do
~H"""
<.form :let={f} for={@for} as={@as} {@rest}>
<div class="space-y-8 bg-white mt-10">
<div class="space-y-8 bg-transparent mt-10">
<%= render_slot(@inner_block, f) %>
<div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6">
<%= render_slot(action, f) %>
@ -220,8 +220,9 @@ defmodule OutlookWeb.CoreComponents do
<button
type={@type}
class={[
"phx-submit-loading:opacity-75 rounded-lg bg-zinc-900 hover:bg-zinc-700 py-2 px-3",
"phx-submit-loading:opacity-75 rounded-lg bg-zinc-900 dark:bg-gray-600 hover:bg-zinc-700 py-2 px-3",
"text-sm font-semibold leading-6 text-white active:text-white/80",
"dark:text-gray-300 dark:active:text-gray/80",
@class
]}
{@rest}
@ -281,7 +282,7 @@ defmodule OutlookWeb.CoreComponents do
assigns = assign_new(assigns, :checked, fn -> input_equals?(assigns.value, "true") end)
~H"""
<label phx-feedback-for={@name} class="flex items-center gap-4 text-sm leading-6 text-zinc-600">
<label phx-feedback-for={@name} class="flex items-center gap-4 text-sm leading-6 text-zinc-600 dark:text-zinc-300">
<input type="hidden" name={@name} value="false" />
<input
type="checkbox"
@ -289,7 +290,10 @@ defmodule OutlookWeb.CoreComponents do
name={@name}
value="true"
checked={@checked}
class="rounded border-zinc-300 text-zinc-900 focus:ring-zinc-900"
class={[
"rounded border-zinc-300 dark:border-stone-800 dark:bg-stone-800 text-zinc-900 dark:text-zinc-200",
" focus:ring-zinc-900 dark:focus:ring-stone-700 dark:focus:bg-stone-800",
]}
{@rest}
/>
<%= @label %>
@ -304,7 +308,7 @@ defmodule OutlookWeb.CoreComponents do
<select
id={@id}
name={@name}
class="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-zinc-500 focus:border-zinc-500 sm:text-sm"
class="mt-1 block w-full py-2 px-3 border border-gray-300 dark:border-gray-700 bg-white dark:bg-stone-900 rounded-md shadow-sm focus:outline-none focus:ring-zinc-500 focus:border-zinc-500 sm:text-sm"
multiple={@multiple}
{@rest}
>
@ -328,6 +332,7 @@ defmodule OutlookWeb.CoreComponents do
"mt-2 block min-h-[6rem] w-full rounded-lg border-zinc-300 py-[7px] px-[11px]",
"text-zinc-900 focus:border-zinc-400 focus:outline-none focus:ring-4 focus:ring-zinc-800/5 sm:text-sm sm:leading-6",
"phx-no-feedback:border-zinc-300 phx-no-feedback:focus:border-zinc-400 phx-no-feedback:focus:ring-zinc-800/5",
"dark:border-gray-700 dark:bg-stone-900 dark:text-gray-300",
@class
]}
{@rest}
@ -360,7 +365,8 @@ defmodule OutlookWeb.CoreComponents do
input_border(@errors),
"mt-2 block w-full rounded-lg border-zinc-300 py-[7px] px-[11px]",
"text-zinc-900 focus:outline-none focus:ring-4 sm:text-sm sm:leading-6",
"phx-no-feedback:border-zinc-300 phx-no-feedback:focus:border-zinc-400 phx-no-feedback:focus:ring-zinc-800/5"
"phx-no-feedback:border-zinc-300 phx-no-feedback:focus:border-zinc-400 phx-no-feedback:focus:ring-zinc-800/5",
"dark:border-gray-700 dark:bg-stone-900 dark:text-gray-300",
]}
{@rest}
/>
@ -383,7 +389,7 @@ defmodule OutlookWeb.CoreComponents do
def label(assigns) do
~H"""
<label for={@for} class="block text-sm font-semibold leading-6 text-zinc-800">
<label for={@for} class="block text-sm font-semibold leading-6 text-zinc-800 dark:text-zinc-400">
<%= render_slot(@inner_block) %>
</label>
"""
@ -458,11 +464,11 @@ defmodule OutlookWeb.CoreComponents do
<th class="relative p-0 pb-4"><span class="sr-only"><%= gettext("Actions") %></span></th>
</tr>
</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
:for={row <- @rows}
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
:for={{col, i} <- Enum.with_index(@col)}
@ -470,11 +476,11 @@ defmodule OutlookWeb.CoreComponents do
class={["p-0", @row_click && "hover:cursor-pointer"]}
>
<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 -right-4 group-hover:bg-zinc-50 sm:rounded-r-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 dark:group-hover:bg-zinc-800 sm:rounded-r-xl" />
</div>
<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) %>
</span>
</div>
@ -483,7 +489,7 @@ defmodule OutlookWeb.CoreComponents do
<div class="relative whitespace-nowrap py-4 text-right text-sm font-medium">
<span
: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) %>
</span>

View File

@ -9,7 +9,7 @@ defmodule OutlookWeb.DarkModeComponent do
def dark_mode_widget(assigns) do
~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">
<button class="p-2" type="button">
@ -41,7 +41,7 @@ defmodule OutlookWeb.DarkModeComponent do
def breakpoint_indicator(assigns) do
~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="hidden sm:inline md:hidden">sm</span>
<span class="hidden md:inline lg:hidden">md</span>

View File

@ -3,6 +3,7 @@ defmodule OutlookWeb.HtmlTreeComponent do
use Phoenix.Component
# use OutlookWeb, :html
import OutlookWeb.CoreComponents
import OutlookWeb.ViewHelpers
alias Phoenix.LiveView.JS
@ -15,10 +16,14 @@ defmodule OutlookWeb.HtmlTreeComponent do
end
def attributes(assigns) do
~H"&nbsp; <%= @name %>=&quot;<%= @value %>&quot;"
~H"&nbsp; <%= @name %>=&quot;<%= elipsed_text(@value, 16) %>&quot;"
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
~H"""
@ -32,7 +37,7 @@ defmodule OutlookWeb.HtmlTreeComponent do
def tnode(assigns) when assigns.node.type == :text do
~H"""
"<%= String.slice(@node.content, 0..35) %><%= if String.length(@node.content) > 35 do %>..."<% end %><br>
"<%= elipsed_text(@node.content, 30) %><br>
"""
end

View File

@ -1,43 +1,9 @@
<header class="px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between border-b border-zinc-100 py-3">
<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">&rarr;</span>
</a>
</div>
</div>
<header class="">
<.breakpoint_indicator :if={Mix.env == :dev} />
<.dark_mode_widget />
</header>
<main class="px-4 py-20 sm:px-6 lg:px-8">
<div class="mx-auto max-w-4xl">
<main class="px-4 sm:px-6 lg:px-8 lg:mx-auto h-screen">
<div class="mx-auto max-w-4xl h-fit">
<.flash kind={:info} title="Success!" flash={@flash} />
<.flash kind={:error} title="Error!" flash={@flash} />
<.flash

View File

@ -8,7 +8,7 @@
</a>
<.dark_mode_widget />
</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">
<.flash kind={:info} title="Success!" flash={@flash} />
<.flash kind={:error} title="Error!" flash={@flash} />

View File

@ -13,7 +13,7 @@
</script>
</head>
<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 %>
<li>
<%= @current_user.email %>

View File

@ -13,7 +13,7 @@ defmodule OutlookWeb.PublicComponents do
def autor(assigns) do
~H"""
<a href={"/autoren/#{@autor.id}"}>
<a href={~p"/autoren/#{@autor}"}>
<div class="p-4 my-2 border rounded-lg border-stone-400 text-stone-800 dark:text-stone-300 ">
<div class="font-bold"><%= @autor.name %></div>
<div class=""><%= @autor.description |> tidy_raw %></div>
@ -23,14 +23,14 @@ defmodule OutlookWeb.PublicComponents do
end
attr :artikel, :any, required: true
attr :show_author, :boolean, default: true
attr :show_autor, :boolean, default: true
def artikel(assigns) do
~H"""
<.link navigate={~p"/artikel/#{@artikel}"}>
<div class="my-2 px-2 rounded border-2 border-solid border-gray-300 dark:border-stone-800">
<h4 class="font-bold text-stone-800 dark:text-stone-300 py-2"><%= @artikel.title %></h4>
<div :if={@show_author}><small><%= @artikel.article.author.name %></small></div>
<div :if={@show_autor}><small><%= @artikel.autor_name %></small></div>
<div><small><%= @artikel.date |> Calendar.strftime("%d.%m.%Y") %></small></div>
<div><%= @artikel.teaser |> tidy_raw %></div>
</div>

View File

@ -9,12 +9,12 @@ defmodule OutlookWeb.TunitEditorComponent do
def tunit_editor(assigns) do
~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">
<%= @current_tunit.content |> raw %>
</div> --%>
<form phx-change="update_current_tunit" phx-target={@target}>
<textarea name="content" class="h-48 rounded border-slate-500 resize-none 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>
</form>
<.status_selector target={@target} disabled={!@current_tunit.status} tunit={@current_tunit} />
@ -24,7 +24,7 @@ defmodule OutlookWeb.TunitEditorComponent do
defp statuses() do
[ {:untranslated, "bg-red-800"},
{:passable, "bg-amber-500"},
{:passable, "bg-amber-500/70"},
{:done, "bg-green-700"} ]
end

View File

@ -1,19 +1,20 @@
defmodule OutlookWeb.ArtikelController do
use OutlookWeb, :controller
alias Outlook.Artikel
alias Outlook.Public
def index(conn, _params) do
artikel = Artikel.list_artikel()
artikel = Public.list_artikel()
render(conn, :index, artikel: artikel, page_title: "Artikel")
end
def show(conn, %{"tid" => tid} = params) do
case Artikel.get_artikel_by_tid(tid) do
case Public.get_artikel_by_tid(tid) do
{:ok, artikel} -> render(conn, :show, artikel: artikel, page_title: artikel.title)
{:error, message} -> conn
|> put_status(404)
|> render(OutlookWeb.ErrorHTML, "404.html")
|> put_view(OutlookWeb.ErrorHTML)
|> render("404.html")
|> halt()
end
end

View File

@ -1,10 +1,10 @@
<header class="mb-6">
<h1 class="text-lg font-semibold leading-tight text-stone-800 dark:text-stone-200"><%= @artikel.title %></h1>
<p class="my-2"><.link href={"/autoren/#{@artikel.article.author.id}"}><%= @artikel.article.author.name %></.link>
&nbsp;&nbsp;&nbsp; — &nbsp;&nbsp;&nbsp;<%= Calendar.strftime(@artikel.article.date, "%d.%m.%Y") %></p>
<p class="my-2"><.link href={~p"/autoren/#{@artikel.autor_id}"}><%= @artikel.autor_name %></.link>
&nbsp;&nbsp;&nbsp; — &nbsp;&nbsp;&nbsp;<%= Calendar.strftime(@artikel.date_org, "%d.%m.%Y") %></p>
<div>Original Artikel:
<.link class="hover:text-sky-700" href={@artikel.article.url} >
<%= @artikel.article.title %>
<.link class="hover:text-sky-700" href={@artikel.url_org} >
<%= @artikel.title_org %>
</.link><br>
</div>
<div class="my-2">
@ -14,4 +14,4 @@
<div class="article w-full mx-auto max-w-xs"><%= @artikel.public_content |> raw %></div>
<.back navigate={~p"/autoren/#{@artikel.article.author}"}>Back to Autor</.back>
<.back navigate={~p"/autoren/#{@artikel.autor_id}"}>Back to Autor</.back>

View File

@ -1,15 +1,15 @@
defmodule OutlookWeb.AutorController do
use OutlookWeb, :controller
alias Outlook.Autoren
alias Outlook.Public
def index(conn, _params) do
autoren = Autoren.list_autoren()
autoren = Public.list_autoren()
render(conn, :index, autoren: autoren, page_title: "Autoren")
end
def show(conn, %{"id" => id}) do
autor = Autoren.get_autor!(id)
autor = Public.get_autor!(id)
# artikel = Autoren.list_artikel(autor)
render(conn, :show, autor: autor, page_title: autor.name)
end

View File

@ -1,13 +1,9 @@
<.header>
<%= @autor.name %>
<:subtitle><div class="text-lg mb-2"><%= @autor.description %></div></:subtitle>
<:subtitle><div class="text-lg mb-2"><%= @autor.description |> tidy_raw %></div></:subtitle>
<:subtitle><.link href={@autor.homepage_url}><%= @autor.homepage_name %></.link></:subtitle>
</.header>
<%= for article <- @autor.articles do %>
<.artikel :for={translation <- article.translations} artikel={translation} show_author={false} />
<% end %>
<.artikel :for={artikel <- @autor.artikel} artikel={artikel} show_autor={false} />
<.back navigate={~p"/autoren"}>Back to autoren</.back>

View File

@ -26,6 +26,7 @@ defmodule OutlookWeb.ArticleLive.FormComponent do
<.input field={{f, :language}} type="select" label="language"
options={Application.get_env(:outlook,:deepl)[:source_langs]} />
<.input field={{f, :date}} type="datetime-local" label="date" />
<.input field={{f, :remarks}} type="textarea" label="remarks" class="h-28" />
<:actions>
<.button phx-disable-with="Saving...">Save Article</.button>
</:actions>

View 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

View File

@ -17,6 +17,7 @@ defmodule OutlookWeb.ArticleLive.New do
|> assign(:raw_html_input, %RawHtmlInput{})
|> assign(:changeset, Articles.change_raw_html_input(%RawHtmlInput{}))
|> assign(:selected_els, [])
|> assign(:selected_tunits, [])
|> assign(:step, :import_raw_html)}
end
@ -26,6 +27,7 @@ defmodule OutlookWeb.ArticleLive.New do
article = %Article{author_id: author.id}
{:noreply,
socket
|> assign(:menu_entries, InternalTree.tunit_modifiers()) # REMOVE ME!
|> assign(:author, author)
|> assign(:article, article)}
end
@ -43,7 +45,9 @@ defmodule OutlookWeb.ArticleLive.New do
{:noreply,
socket
|> 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)}
false ->
{:noreply, assign(socket, :changeset, changeset)}
@ -54,10 +58,42 @@ defmodule OutlookWeb.ArticleLive.New do
def handle_event("approve_raw_internaltree", _, socket) do
socket = socket
|> 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)}
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
def handle_event("approve_translation_units", _, socket) do
{:noreply, socket |> assign(:step, :final_form)}

View File

@ -4,7 +4,7 @@
<.import_raw_html :if={@step == :import_raw_html} changeset={@changeset} />
<.review_raw_internaltree :if={@step == :review_raw_internaltree} raw_internal_tree={@raw_internal_tree} />
<.review_translation_units :if={@step == :review_translation_units} />
<.review_translation_units :if={@step == :review_translation_units} raw_internal_tree={@raw_internal_tree} menu_entries={@menu_entries} />
<.live_component
:if={@step == :final_form}
module={OutlookWeb.ArticleLive.FormComponent}

View File

@ -31,7 +31,7 @@ defmodule OutlookWeb.ArticleLive.NewComponents do
<div>Review Raw InternalTree</div>
<div class="flex">
<div id="html-preview" class="article">
<%= InternalTree.render_html_preview(@raw_internal_tree) |> raw %>
<.render_doc tree={@raw_internal_tree} ></.render_doc>
</div>
<div id="html-tree">
<.render_tree tree={@raw_internal_tree} ></.render_tree>
@ -44,6 +44,17 @@ defmodule OutlookWeb.ArticleLive.NewComponents do
def review_translation_units(assigns) do
~H"""
<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>
"""
end

View File

@ -16,7 +16,7 @@
</:actions>
</.header>
<.table id="translations" rows={@article.translations} row_click={&JS.navigate(~p"/translations/#{(&1).id}")}>
<.table id="translations" rows={@article.translations} row_click={&JS.navigate(~p"/translations/#{&1}")}>
<:col :let={translation} label="Language"><%= translation.language %></:col>
<:col :let={translation} label="Title"><%= translation.title %></:col>
<:col :let={translation} label="Teaser"><%= translation.teaser |> tidy_raw %></:col>
@ -24,9 +24,9 @@
<:col :let={translation} label="Public"><%= translation.public %></:col>
<:action :let={translation}>
<div class="sr-only">
<.link navigate={~p"/translations/#{translation.id}"}>Show</.link>
<.link navigate={~p"/translations/#{translation}"}>Show</.link>
</div>
<.link navigate={~p"/translations/#{translation.id}/edit"}>Edit</.link>
<.link navigate={~p"/translations/#{translation}/edit"}>Edit</.link>
</:action>
<:action :let={translation}>
<.link phx-click={JS.push("delete_translation", value: %{id: translation.id})} data-confirm="Are you sure?">
@ -40,6 +40,10 @@
<a href="#" class="hide-link" phx-click={JS.remove_class("show-boundary", to: ".article")}>hide boundaries</a>
<.render_doc tree={@article_content} />
</div>
<div class="my-4">
<div>Remarks:</div>
<%= @article.remarks |> tidy_raw %>
</div>
<div class="h-10" />
<.link class="text-sm font-semibold" navigate={~p"/translations/new?article_id=#{@article.id}"}>New Translation</.link>

View File

@ -8,12 +8,13 @@ defmodule OutlookWeb.TranslationLive.FormComponent do
def render(assigns) do
~H"""
<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>
<%= @title %>
<:subtitle>Use this form to manage translation records in your database.</:subtitle>
</.header>
<div phx-click={JS.toggle(to: ".more-fields")} class="cursor-pointer">more/less fields</div>
<.simple_form
:let={f}
for={@changeset}
@ -23,29 +24,34 @@ defmodule OutlookWeb.TranslationLive.FormComponent do
phx-submit="save"
>
<.input field={{f, :article_id}} type="hidden" />
<div class="more-fields">
<.input field={{f, :language}} type="select" label="language"
options={Application.get_env(:outlook,:deepl)[:target_langs]} />
</div>
<.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, :date}} type="datetime-local" label="date" />
<div class="flex items-center justify-between">
<.input field={{f, :public}} type="checkbox" label="public" />
<.input field={{f, :unauthorized}} type="checkbox" label="unauthorized" />
</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="publish" name="publish" value="false" />
<:actions>
<.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")}
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")}
phx-disable-with="Saving...">Save and Edit</.button>
id="save-edit-button" phx-disable-with="Saving...">Save and Edit</.button>
</:actions>
</.simple_form>
<.tunit_editor current_tunit={@current_tunit} target={@myself} />
</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}
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} />
@ -63,6 +69,7 @@ defmodule OutlookWeb.TranslationLive.FormComponent do
socket
|> assign(assigns)
|> assign(:current_tunit, %TranslationUnit{status: nil})
|> assign(:tunit_ids, InternalTree.get_tunit_ids(translation.article.content))
|> assign(:changeset, changeset)
|> assign_article_tree(translation)
|> assign(:deepl_progress, nil)}
@ -118,11 +125,33 @@ defmodule OutlookWeb.TranslationLive.FormComponent do
{:noreply, socket |> assign(:current_tunit, tunit)}
end
def handle_event("select_current_tunit", %{"nid" => nid}, socket) do
{:noreply,
def handle_event("select_tunit_by_nid", %{"nid" => nid}, socket) do
{: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
|> assign(:article_tree, InternalTree.garnish(socket.assigns.article_tree, %{tunits: %{current: fun}}))
|> 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
@doc "updating on browser events"
@ -146,7 +175,7 @@ defmodule OutlookWeb.TranslationLive.FormComponent do
:article_tree,
InternalTree.add_phx_click_event(translation.article.content,
nodes: :tunits,
click: "select_current_tunit",
click: "select_tunit_by_nid",
target: socket.assigns.myself)
|> InternalTree.garnish(%{tunits: %{class: "tunit"}}))
end
@ -187,6 +216,7 @@ defmodule OutlookWeb.TranslationLive.FormComponent do
defp continue_edit(socket, :edit, %{"continue_edit" => "true"}) do
socket
|> assign(:translation, Translations.get_translation!(socket.assigns.translation.id))
end
defp continue_edit(socket, :new, %{"continue_edit" => "true"} = params) do
socket |> push_patch(to: ~p(/translations/#{params["id"]}/edit))

View File

@ -2,7 +2,7 @@
Listing Translations
</.header>
<.table id="translations" rows={@translations} row_click={&JS.navigate(~p(/translations/#{(&1).id}))}>
<.table id="translations" rows={@translations} row_click={&JS.navigate(~p(/translations/#{&1}))}>
<:col :let={translation} label="Language"><%= translation.language %></:col>
<:col :let={translation} label="Title"><%= translation.title %></:col>
<:col :let={translation} label="Teaser"><%= translation.teaser |> tidy_raw %></:col>
@ -12,9 +12,9 @@
<:col :let={translation} label="Unauthorized"><%= translation.unauthorized %></:col>
<:action :let={translation}>
<div class="sr-only">
<.link navigate={~p"/translations/#{translation.id}"}>Show</.link>
<.link navigate={~p"/translations/#{translation}"}>Show</.link>
</div>
<.link navigate={~p"/translations/#{translation.id}/edit"}>Edit</.link>
<.link navigate={~p"/translations/#{translation}/edit"}>Edit</.link>
</:action>
<:action :let={translation}>
<.link phx-click={JS.push("delete", value: %{id: translation.id})} data-confirm="Are you sure?">

View File

@ -10,10 +10,18 @@ defmodule OutlookWeb.TranslationLive.Show do
@impl true
def handle_params(%{"id" => id}, _, socket) do
translation = Translations.get_translation!(id)
{:noreply,
socket
|> 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
defp page_title(:show), do: "Show Translation"

View File

@ -2,7 +2,7 @@
Translation <%= @translation.id %>
<:subtitle>This is a translation record from your database.</:subtitle>
<:actions>
<.link navigate={~p"/translations/#{@translation.id}/edit"} phx-click={JS.push_focus()}>
<.link navigate={~p"/translations/#{@translation}/edit"} phx-click={JS.push_focus()}>
<.button>Edit translation</.button>
</.link>
</:actions>
@ -11,15 +11,16 @@
<.list>
<:item title="Language"><%= @translation.language %></: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="Date"><%= @translation.date %></:item>
<:item title="Public"><%= @translation.public %></:item>
<:item title="Unauthorized"><%= @translation.unauthorized %></:item>
<:item title="Remarks"><%= @translation.remarks |> tidy_raw %></:item>
</.list>
<div class="article">
<.render_doc tree={InternalTree.render_translation(@translation.article.content, @translation.content)} />
<div class="article show_status">
<.render_doc tree={@translation_tree} />
</div>
<.back navigate={~p"/articles/#{@translation.article}"}>Back to <article></article></.back>

View File

@ -3,10 +3,28 @@ defmodule OutlookWeb.ViewHelpers do
import Phoenix.HTML, only: [raw: 1]
@doc "Just sanitize tags"
def tidy_raw(html) do
def tidy_raw(html) when is_binary(html) do
html
|> Floki.parse_fragment!()
|> Floki.raw_html()
|> raw
end
def tidy_raw(whatever) do
whatever
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

View File

@ -12,14 +12,14 @@
"ecto": {:hex, :ecto, "3.9.4", "3ee68e25dbe0c36f980f1ba5dd41ee0d3eb0873bccae8aeaf1a2647242bffa35", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de5f988c142a3aa4ec18b85a4ec34a2390b65b24f02385c1144252ff6ff8ee75"},
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.10", "e14d400930f401ca9f541b3349212634e44027d7f919bbb71224d7ac0d0e8acd", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.15.7 or ~> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "505e8cd81e4f17c090be0f99e92b1b3f0fd915f98e76965130b8ccfb891e7088"},
"ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"},
"elixir_make": {:hex, :elixir_make, "0.7.3", "c37fdae1b52d2cc51069713a58c2314877c1ad40800a57efb213f77b078a460d", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "24ada3e3996adbed1fa024ca14995ef2ba3d0d17b678b0f3f2b1f66e6ce2b274"},
"elixir_make": {:hex, :elixir_make, "0.7.5", "784cc00f5fa24239067cc04d449437dcc5f59353c44eb08f188b2b146568738a", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "c3d63e8d5c92fa3880d89ecd41de59473fa2e83eeb68148155e25e8b95aa2887"},
"esbuild": {:hex, :esbuild, "0.6.1", "a774bfa7b4512a1211bf15880b462be12a4c48ed753a170c68c63b2c95888150", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "569f7409fb5a932211573fc20e2a930a0d5cf3377c5b4f6506c651b1783a1678"},
"expo": {:hex, :expo, "0.3.0", "13127c1d5f653b2927f2616a4c9ace5ae372efd67c7c2693b87fd0fdc30c6feb", [:mix], [], "hexpm", "fb3cd4bf012a77bc1608915497dae2ff684a06f0fa633c7afa90c4d72b881823"},
"expo": {:hex, :expo, "0.4.0", "bbe4bf455e2eb2ebd2f1e7d83530ce50fb9990eb88fc47855c515bfdf1c6626f", [:mix], [], "hexpm", "a8ed1683ec8b7c7fa53fd7a41b2c6935f539168a6bb0616d7fd6b58a36f3abf2"},
"fast_html": {:hex, :fast_html, "2.0.5", "c61760340606c1077ff1f196f17834056cb1dd3d5cb92a9f2cabf28bc6221c3c", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "605f4f4829443c14127694ebabb681778712ceecb4470ec32aa31012330e6506"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"finch": {:hex, :finch, "0.14.0", "619bfdee18fc135190bf590356c4bf5d5f71f916adb12aec94caa3fa9267a4bc", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5459acaf18c4fdb47a8c22fb3baff5d8173106217c8e56c5ba0b93e66501a8dd"},
"floki": {:hex, :floki, "0.34.1", "b1f9c413d91140230788b173906065f6f8906bbbf5b3f0d3c626301aeeef44c5", [:mix], [], "hexpm", "cc9b62312a45c1239ca8f65e05377ef8c646f3d7712e5727a9b47c43c946e885"},
"gettext": {:hex, :gettext, "0.22.0", "a25d71ec21b1848957d9207b81fd61cb25161688d282d58bdafef74c2270bdc4", [:mix], [{:expo, "~> 0.3.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "cb0675141576f73720c8e49b4f0fd3f2c69f0cd8c218202724d4aebab8c70ace"},
"floki": {:hex, :floki, "0.34.2", "5fad07ef153b3b8ec110b6b155ec3780c4b2c4906297d0b4be1a7162d04a7e02", [:mix], [], "hexpm", "26b9d50f0f01796bc6be611ca815c5e0de034d2128e39cc9702eee6b66a4d1c8"},
"gettext": {:hex, :gettext, "0.22.1", "e7942988383c3d9eed4bdc22fc63e712b655ae94a672a27e4900e3d4a2c43581", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "ad105b8dab668ee3f90c0d3d94ba75e9aead27a62495c101d94f2657a190ac5d"},
"hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~> 2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
"heroicons": {:hex, :heroicons, "0.5.2", "a7ae72460ecc4b74a4ba9e72f0b5ac3c6897ad08968258597da11c2b0b210683", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.2", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "7ef96f455c1c136c335f1da0f1d7b12c34002c80a224ad96fc0ebf841a6ffef5"},
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
@ -34,12 +34,12 @@
"nimble_options": {:hex, :nimble_options, "0.5.2", "42703307b924880f8c08d97719da7472673391905f528259915782bb346e0a1b", [:mix], [], "hexpm", "4da7f904b915fd71db549bcdc25f8d56f378ef7ae07dc1d372cbe72ba950dce0"},
"nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
"phoenix": {:hex, :phoenix, "1.7.0-rc.2", "8faaff6f699aad2fe6a003c627da65d0864c868a4c10973ff90abfd7286c1f27", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.4", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "71abde2f67330c55b625dcc0e42bf76662dbadc7553c4f545c2f3759f40f7487"},
"phoenix": {:hex, :phoenix, "1.7.0", "cbed113bdc203e2ced75859011fe7e71eeebb6259cefa54de810d9c7048b5e22", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.4", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8526139d4bd79ec97c5c3c8e69f6cd663597f782756cec874ba7da5429c93e34"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
"phoenix_html": {:hex, :phoenix_html, "3.3.0", "bf451c71ebdaac8d2f40d3b703435e819ccfbb9ff243140ca3bd10c155f134cc", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "272c5c1533499f0132309936c619186480bafcc2246588f99a69ce85095556ef"},
"phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.13", "956aed3ffe24b9ecd5e4bde10fdc9673b77f43adf4d1172a6812abad35dbc94c", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9ecbe76c79102565a14e9fe54aac4b086991dbd5e8da7da4d4d6442f4e79147f"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.16", "781c6a3ac49e0451ca403848b40807171caea400896fe8ed8e5ddd6106ad5580", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "09e6ae2babe62f74bfcd1e3cac1a9b0e2c262557cc566300a843425c9cb6842a"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
"phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"},
"plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"},

View File

@ -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

View File

@ -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

View 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

View 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

View 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

View 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

View File

@ -4,6 +4,8 @@ defmodule Outlook.ArticlesFixtures do
entities via the `Outlook.Articles` context.
"""
# TODO: make this work
@doc """
Generate a article.
"""
@ -11,11 +13,17 @@ defmodule Outlook.ArticlesFixtures do
{:ok, article} =
attrs
|> 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],
language: "some language",
language: "EN",
title: "some title",
url: "some url"
url: "some url",
author_id: 1
})
|> Outlook.Articles.create_article()

View 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

View File

@ -4,6 +4,8 @@ defmodule Outlook.TranslationsFixtures do
entities via the `Outlook.Translations` context.
"""
# TODO: make this work
@doc """
Generate a translation.
"""
@ -17,7 +19,8 @@ defmodule Outlook.TranslationsFixtures do
public: true,
teaser: "some teaser",
title: "some title",
unauthorized: true
unauthorized: true,
article_id: 1
})
|> Outlook.Translations.create_translation()