Compare commits
58 Commits
8f2698bf7b
...
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 | |||
| 3fe4a331ac | |||
| 6d0ae825d8 | |||
| 3a2769eed1 | |||
| e8089eb24e | |||
| 02e6340c0a | |||
| ed98f4cbc4 | |||
| b87b54ec71 | |||
| fc0818678c | |||
| dc4c8e4790 | |||
| e98187200a | |||
| b47d7d081d | |||
| 2ffea3e490 | |||
| cb8e9ef14f | |||
| faf2bb0e2e | |||
| 7297a907da | |||
| 66a61c8380 | |||
| b0267ef752 | |||
| 239177db50 | |||
| 1b68a1de16 | |||
| 6ae1289400 | |||
| 666c499c72 | |||
| 7cd9a09cfd | |||
| 32df023891 | |||
| 38000db27a |
22
.iex-local.exs
Normal 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>
|
||||||
|
"""
|
||||||
@ -1,10 +1,49 @@
|
|||||||
|
.main {
|
||||||
|
@apply place-content-center;
|
||||||
|
}
|
||||||
|
|
||||||
.article {
|
.article {
|
||||||
/* @apply pr-8 */
|
/* @apply pr-8 */
|
||||||
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 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 {
|
.article a.hide-link {
|
||||||
@ -28,7 +67,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.article h4 {
|
.article h4 {
|
||||||
@apply my-2 font-semibold text-lg leading-8 text-zinc-800;
|
@apply my-2 font-semibold text-lg leading-8 text-stone-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .article h4 {
|
||||||
|
@apply text-stone-300;
|
||||||
}
|
}
|
||||||
|
|
||||||
.article p, .article div {
|
.article p, .article div {
|
||||||
@ -47,6 +90,10 @@
|
|||||||
@apply text-cyan-900;
|
@apply text-cyan-900;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark .article a {
|
||||||
|
@apply text-cyan-700;
|
||||||
|
}
|
||||||
|
|
||||||
.article ul {
|
.article ul {
|
||||||
@apply pl-6 list-disc my-2;
|
@apply pl-6 list-disc my-2;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -2,25 +2,29 @@ let DarkModeHook = {
|
|||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.button = this.el.querySelector("button")
|
this.button = this.el.querySelector("button")
|
||||||
this.button.addEventListener("click", this.show_selector.bind(this))
|
this.button.addEventListener("click", this.toggle_chooser.bind(this))
|
||||||
this.selector = this.el.querySelector("ul")
|
this.chooser = this.el.querySelector("ul")
|
||||||
var lis = this.el.querySelectorAll("li")
|
var lis = this.el.querySelectorAll("li")
|
||||||
lis[0].addEventListener("click", this.switch_to_day_mode.bind(this))
|
lis[0].addEventListener("click", this.switch_to_day_mode.bind(this))
|
||||||
lis[1].addEventListener("click", this.switch_to_night_mode.bind(this))
|
lis[1].addEventListener("click", this.switch_to_night_mode.bind(this))
|
||||||
lis[2].addEventListener("click", this.switch_to_system_mode.bind(this))
|
lis[2].addEventListener("click", this.switch_to_system_mode.bind(this))
|
||||||
},
|
},
|
||||||
show_selector(){
|
toggle_chooser(){
|
||||||
this.selector.classList.remove("hidden")
|
if (this.chooser.classList.contains("hidden")){
|
||||||
|
this.chooser.classList.remove("hidden")
|
||||||
|
} else {
|
||||||
|
this.chooser.classList.add("hidden")
|
||||||
|
}
|
||||||
},
|
},
|
||||||
switch_to_day_mode() {
|
switch_to_day_mode() {
|
||||||
document.documentElement.classList.remove('dark')
|
document.documentElement.classList.remove('dark')
|
||||||
localStorage.theme = 'light'
|
localStorage.theme = 'light'
|
||||||
this.selector.classList.add("hidden")
|
this.chooser.classList.add("hidden")
|
||||||
},
|
},
|
||||||
switch_to_night_mode() {
|
switch_to_night_mode() {
|
||||||
document.documentElement.classList.add('dark')
|
document.documentElement.classList.add('dark')
|
||||||
localStorage.theme = 'dark'
|
localStorage.theme = 'dark'
|
||||||
this.selector.classList.add("hidden")
|
this.chooser.classList.add("hidden")
|
||||||
},
|
},
|
||||||
switch_to_system_mode() {
|
switch_to_system_mode() {
|
||||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||||
@ -29,7 +33,7 @@ let DarkModeHook = {
|
|||||||
document.documentElement.classList.remove('dark')
|
document.documentElement.classList.remove('dark')
|
||||||
}
|
}
|
||||||
localStorage.removeItem('theme')
|
localStorage.removeItem('theme')
|
||||||
this.selector.classList.add("hidden")
|
this.chooser.classList.add("hidden")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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}
|
||||||
@ -45,7 +45,7 @@ defmodule Outlook.Accounts.User do
|
|||||||
defp validate_email(changeset, opts) do
|
defp validate_email(changeset, opts) do
|
||||||
changeset
|
changeset
|
||||||
|> validate_required([:email])
|
|> validate_required([:email])
|
||||||
|> validate_format(:email, ~r/^[^\s]+@[^\s]+$/, message: "must have the @ sign and no spaces")
|
|> validate_format(:email, ~r/^[^\s]+@[^\s]+$/u, message: "must have the @ sign and no spaces")
|
||||||
|> validate_length(:email, max: 160)
|
|> validate_length(:email, max: 160)
|
||||||
|> maybe_validate_unique_email(opts)
|
|> maybe_validate_unique_email(opts)
|
||||||
end
|
end
|
||||||
@ -53,10 +53,10 @@ defmodule Outlook.Accounts.User do
|
|||||||
defp validate_password(changeset, opts) do
|
defp validate_password(changeset, opts) do
|
||||||
changeset
|
changeset
|
||||||
|> validate_required([:password])
|
|> validate_required([:password])
|
||||||
|> validate_length(:password, min: 12, max: 72)
|
|> validate_length(:password, min: 8, max: 72)
|
||||||
# |> validate_format(:password, ~r/[a-z]/, message: "at least one lower case character")
|
|> validate_format(:password, ~r/[a-z]/u, message: "at least one lower case character")
|
||||||
# |> validate_format(:password, ~r/[A-Z]/, message: "at least one upper case character")
|
|> validate_format(:password, ~r/[A-Z]/u, message: "at least one upper case character")
|
||||||
# |> validate_format(:password, ~r/[!?@#$%^&*_0-9]/, message: "at least one digit or punctuation character")
|
|> validate_format(:password, ~r/[!?@#$%^&*_0-9]/u, message: "at least one digit or punctuation character")
|
||||||
|> maybe_hash_password(opts)
|
|> maybe_hash_password(opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -1,21 +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
|
|
||||||
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,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
|
|
||||||
@ -63,7 +63,7 @@ defmodule Outlook.HtmlPreparations.HtmlPreparation do
|
|||||||
|
|
||||||
def set_sibling_with([ node | rest ]) do
|
def set_sibling_with([ node | rest ]) do
|
||||||
sib_with = case node.type do
|
sib_with = case node.type do
|
||||||
:text -> Regex.match?(~r/^\s*$/, node.content) && :both || :inline
|
:text -> Regex.match?(~r/^\s*$/u, node.content) && :both || :inline
|
||||||
:comment -> :both
|
:comment -> :both
|
||||||
end
|
end
|
||||||
[ %InternalNode{ node | eph: %{sibling_with: sib_with} } | set_sibling_with(rest) ]
|
[ %InternalNode{ node | eph: %{sibling_with: sib_with} } | set_sibling_with(rest) ]
|
||||||
@ -73,7 +73,7 @@ defmodule Outlook.HtmlPreparations.HtmlPreparation do
|
|||||||
|
|
||||||
|
|
||||||
def strip_whitespace_textnodes [ %{type: :text} = node | rest] do
|
def strip_whitespace_textnodes [ %{type: :text} = node | rest] do
|
||||||
if Regex.match?(~r/^\s*$/, node.content) do
|
if Regex.match?(~r/^\s*$/u, node.content) do
|
||||||
strip_whitespace_textnodes(rest)
|
strip_whitespace_textnodes(rest)
|
||||||
else
|
else
|
||||||
[ node | strip_whitespace_textnodes(rest)]
|
[ node | strip_whitespace_textnodes(rest)]
|
||||||
|
|||||||
@ -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})
|
||||||
|
|||||||
@ -58,4 +58,19 @@ defmodule Outlook.InternalTree.InternalTree do
|
|||||||
|> Enum.into(node_atts)
|
|> Enum.into(node_atts)
|
||||||
%{node | eph: Map.put(node.eph, :attributes, attributes)}
|
%{node | eph: Map.put(node.eph, :attributes, attributes)}
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
@ -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
@ -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
@ -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
|
||||||
43
lib/outlook/public/artikel.ex
Normal 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
|
||||||
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],
|
||||||
|
|||||||
@ -66,6 +66,11 @@ defmodule Outlook.Translators do
|
|||||||
Task.start_link(Deepl, :translate, args)
|
Task.start_link(Deepl, :translate, args)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def process_translation_result(result, tunit_ids, current_user) do
|
||||||
|
increase_our_character_count(current_user, result.billed_characters)
|
||||||
|
process_translation(result.translation, tunit_ids)
|
||||||
|
end
|
||||||
|
|
||||||
defp deepl_account_for_user(user) when is_struct(user), do: deepl_account_for_user(user.id)
|
defp deepl_account_for_user(user) when is_struct(user), do: deepl_account_for_user(user.id)
|
||||||
defp deepl_account_for_user(user_id) do
|
defp deepl_account_for_user(user_id) do
|
||||||
DeeplAccount |> where(user_id: ^user_id)
|
DeeplAccount |> where(user_id: ^user_id)
|
||||||
@ -78,12 +83,7 @@ defmodule Outlook.Translators do
|
|||||||
|> IO.iodata_to_binary()
|
|> IO.iodata_to_binary()
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_translation_result(result, tunit_ids) do
|
defp process_translation(translation, tunit_ids) do
|
||||||
# TODO: update :our_character_count
|
|
||||||
process_translation(result.translation, tunit_ids)
|
|
||||||
end
|
|
||||||
|
|
||||||
def 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}})
|
||||||
|
|||||||
@ -86,11 +86,15 @@ defmodule OutlookWeb do
|
|||||||
import Phoenix.HTML
|
import Phoenix.HTML
|
||||||
# Core UI components and translation
|
# Core UI components and translation
|
||||||
import OutlookWeb.CoreComponents
|
import OutlookWeb.CoreComponents
|
||||||
|
|
||||||
|
# custom components and module
|
||||||
import OutlookWeb.HtmlTreeComponent
|
import OutlookWeb.HtmlTreeComponent
|
||||||
import OutlookWeb.HtmlDocComponent
|
import OutlookWeb.HtmlDocComponent
|
||||||
import OutlookWeb.TunitEditorComponent
|
import OutlookWeb.TunitEditorComponent
|
||||||
import OutlookWeb.PublicComponents
|
import OutlookWeb.PublicComponents
|
||||||
import OutlookWeb.DarkModeComponent
|
import OutlookWeb.DarkModeComponent
|
||||||
|
import OutlookWeb.ViewHelpers
|
||||||
|
|
||||||
import OutlookWeb.Gettext
|
import OutlookWeb.Gettext
|
||||||
|
|
||||||
# Shortcut for generating JS commands
|
# Shortcut for generating JS commands
|
||||||
|
|||||||
@ -191,7 +191,7 @@ defmodule OutlookWeb.CoreComponents do
|
|||||||
def simple_form(assigns) do
|
def simple_form(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<.form :let={f} for={@for} as={@as} {@rest}>
|
<.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) %>
|
<%= render_slot(@inner_block, f) %>
|
||||||
<div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6">
|
<div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6">
|
||||||
<%= render_slot(action, f) %>
|
<%= render_slot(action, f) %>
|
||||||
@ -220,8 +220,9 @@ defmodule OutlookWeb.CoreComponents do
|
|||||||
<button
|
<button
|
||||||
type={@type}
|
type={@type}
|
||||||
class={[
|
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",
|
"text-sm font-semibold leading-6 text-white active:text-white/80",
|
||||||
|
"dark:text-gray-300 dark:active:text-gray/80",
|
||||||
@class
|
@class
|
||||||
]}
|
]}
|
||||||
{@rest}
|
{@rest}
|
||||||
@ -281,7 +282,7 @@ defmodule OutlookWeb.CoreComponents do
|
|||||||
assigns = assign_new(assigns, :checked, fn -> input_equals?(assigns.value, "true") end)
|
assigns = assign_new(assigns, :checked, fn -> input_equals?(assigns.value, "true") end)
|
||||||
|
|
||||||
~H"""
|
~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="hidden" name={@name} value="false" />
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@ -289,7 +290,10 @@ defmodule OutlookWeb.CoreComponents do
|
|||||||
name={@name}
|
name={@name}
|
||||||
value="true"
|
value="true"
|
||||||
checked={@checked}
|
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}
|
{@rest}
|
||||||
/>
|
/>
|
||||||
<%= @label %>
|
<%= @label %>
|
||||||
@ -304,7 +308,7 @@ defmodule OutlookWeb.CoreComponents do
|
|||||||
<select
|
<select
|
||||||
id={@id}
|
id={@id}
|
||||||
name={@name}
|
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}
|
multiple={@multiple}
|
||||||
{@rest}
|
{@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]",
|
"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",
|
"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",
|
"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
|
@class
|
||||||
]}
|
]}
|
||||||
{@rest}
|
{@rest}
|
||||||
@ -360,7 +365,8 @@ defmodule OutlookWeb.CoreComponents do
|
|||||||
input_border(@errors),
|
input_border(@errors),
|
||||||
"mt-2 block w-full rounded-lg border-zinc-300 py-[7px] px-[11px]",
|
"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",
|
"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}
|
{@rest}
|
||||||
/>
|
/>
|
||||||
@ -383,7 +389,7 @@ defmodule OutlookWeb.CoreComponents do
|
|||||||
|
|
||||||
def label(assigns) do
|
def label(assigns) do
|
||||||
~H"""
|
~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) %>
|
<%= render_slot(@inner_block) %>
|
||||||
</label>
|
</label>
|
||||||
"""
|
"""
|
||||||
@ -416,10 +422,10 @@ defmodule OutlookWeb.CoreComponents do
|
|||||||
~H"""
|
~H"""
|
||||||
<header class={[@actions != [] && "flex items-center justify-between gap-6", @class]}>
|
<header class={[@actions != [] && "flex items-center justify-between gap-6", @class]}>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-lg font-semibold leading-8 text-zinc-800">
|
<h1 class="text-lg font-semibold leading-8 text-stone-800 dark:text-stone-300 ">
|
||||||
<%= render_slot(@inner_block) %>
|
<%= render_slot(@inner_block) %>
|
||||||
</h1>
|
</h1>
|
||||||
<p :if={@subtitle != []} class="mt-2 text-sm leading-6 text-zinc-600">
|
<p :if={@subtitle != []} class="mt-2 text-sm leading-6 text-stone-600">
|
||||||
<%= render_slot(@subtitle) %>
|
<%= render_slot(@subtitle) %>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -458,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)}
|
||||||
@ -470,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>
|
||||||
@ -483,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>
|
||||||
@ -538,7 +544,7 @@ defmodule OutlookWeb.CoreComponents do
|
|||||||
<div class="mt-16">
|
<div class="mt-16">
|
||||||
<.link
|
<.link
|
||||||
navigate={@navigate}
|
navigate={@navigate}
|
||||||
class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
|
class="text-sm font-semibold leading-6 text-stone-900 hover:text-stone-700 dark:text-stone-300 hover:dark:text-stone-200"
|
||||||
>
|
>
|
||||||
<Heroicons.arrow_left solid class="w-3 h-3 stroke-current inline" />
|
<Heroicons.arrow_left solid class="w-3 h-3 stroke-current inline" />
|
||||||
<%= render_slot(@inner_block) %>
|
<%= render_slot(@inner_block) %>
|
||||||
|
|||||||
@ -9,29 +9,45 @@ defmodule OutlookWeb.DarkModeComponent do
|
|||||||
|
|
||||||
def dark_mode_widget(assigns) do
|
def dark_mode_widget(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div id="dark-mode-widget" class="relative flex flex-row-reverse p-4" phx-hook="dark_mode_widget">
|
<div id="dark-mode-widget" class="relative flex w-full justify-between p-0" phx-hook="dark_mode_widget">
|
||||||
<button type="button" id="headlessui-listbox-button-4" aria-haspopup="true" aria-expanded="true" data-headlessui-state="open" >
|
<div class="flex"></div>
|
||||||
<span class="dark:hidden">
|
<div class="flex">
|
||||||
<Heroicons.sun class="w-5 h-5 stroke-slate-400 dark:stroke-slate-500" />
|
<button class="p-2" type="button">
|
||||||
</span>
|
<span class="dark:hidden">
|
||||||
<span class="hidden dark:inline">
|
<Heroicons.sun class="w-5 h-5 stroke-stone-900 dark:stroke-stone-500" />
|
||||||
<Heroicons.moon class="w-5 h-5 stroke-slate-400 dark:stroke-slate-500" />
|
</span>
|
||||||
</span>
|
<span class="hidden dark:inline">
|
||||||
</button>
|
<Heroicons.moon class="w-5 h-5 stroke-stone-900 dark:stroke-stone-500" />
|
||||||
<ul class="hidden absolute z-50 top right-2 bg-white rounded-lg ring-1 ring-slate-900/10 shadow-lg overflow-hidden w-36 py-1 text-sm text-slate-700 font-semibold dark:bg-slate-800 dark:ring-0 dark:highlight-white/5 dark:text-slate-300 mt-8" aria-labelledby="headlessui-listbox-label-3" aria-orientation="vertical" id="headlessui-listbox-options-5" role="listbox" tabindex="0" data-headlessui-state="open">
|
</span>
|
||||||
<li class="py-1 px-2 flex items-center cursor-pointer" id="selector-option-1" role="option" tabindex="-1" aria-selected="false" data-headlessui-state="">
|
</button>
|
||||||
<Heroicons.sun class="w-5 h-5 mr-2 stroke-slate-400 dark:stroke-slate-500" />
|
<ul class="hidden absolute z-50 top right-2 bg-white rounded-lg ring-1 ring-slate-900/10 shadow-lg overflow-hidden w-36 py-1 text-sm text-slate-700 font-semibold dark:bg-slate-800 dark:ring-0 dark:highlight-white/5 dark:text-slate-300 mt-8" aria-labelledby="headlessui-listbox-label-3" aria-orientation="vertical" id="headlessui-listbox-options-5" role="listbox" tabindex="0" data-headlessui-state="open">
|
||||||
Light
|
<li class="py-1 px-2 flex items-center cursor-pointer" id="selector-option-1" role="option" tabindex="-1">
|
||||||
</li>
|
<Heroicons.sun class="w-5 h-5 mr-2 stroke-slate-400 dark:stroke-slate-500" />
|
||||||
<li class="py-1 px-2 flex items-center cursor-pointer" id="selector-option-2" role="option" tabindex="-1" aria-selected="false" data-headlessui-state="">
|
Light
|
||||||
<Heroicons.moon class="w-5 h-5 mr-2 stroke-slate-400 dark:stroke-slate-500" />
|
</li>
|
||||||
Dark
|
<li class="py-1 px-2 flex items-center cursor-pointer" id="selector-option-2" role="option" tabindex="-1">
|
||||||
</li>
|
<Heroicons.moon class="w-5 h-5 mr-2 stroke-slate-400 dark:stroke-slate-500" />
|
||||||
<li class="py-1 px-2 flex items-center cursor-pointer text-slate-700 dark:text-slate-300" id="selector-option-3" role="option" tabindex="-1" aria-selected="true" data-headlessui-state="selected">
|
Dark
|
||||||
<Heroicons.computer_desktop class="w-5 h-5 mr-2 stroke-slate-400 dark:stroke-slate-500" />
|
</li>
|
||||||
System
|
<li class="py-1 px-2 flex items-center cursor-pointer text-slate-700 dark:text-slate-300" id="selector-option-3" role="option" tabindex="-1" aria-selected="true" data-headlessui-state="selected">
|
||||||
</li>
|
<Heroicons.computer_desktop class="w-5 h-5 mr-2 stroke-slate-400 dark:stroke-slate-500" />
|
||||||
</ul>
|
System
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
def breakpoint_indicator(assigns) do
|
||||||
|
~H"""
|
||||||
|
<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>
|
||||||
|
<span class="hidden lg:inline xl:hidden">lg</span>
|
||||||
|
<span class="hidden xl:inline 2xl:hidden">xl</span>
|
||||||
|
<span class="hidden 2xl:inline">2xl</span>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
<header class="">
|
<header class="">
|
||||||
|
<.breakpoint_indicator :if={Mix.env == :dev} />
|
||||||
<a href="/">
|
<a href="/">
|
||||||
<img class="w-full" src="/images/elbefoto-lg.jpg"
|
<img class="w-full dark:hidden" src="/images/elbefoto-lg.jpg"
|
||||||
src-set="elbefoto-xxl.jpg 4496w, /images/elbefoto-lg.jpg 2248w, /images/elbefoto-md.jpg 1199w, /images/elbefoto-sm.jpg 991w, /images/elbefoto-xs.jpg 767w">
|
src-set="elbefoto-xxl.jpg 4496w, /images/elbefoto-lg.jpg 2248w, /images/elbefoto-md.jpg 1199w, /images/elbefoto-sm.jpg 991w, /images/elbefoto-xs.jpg 767w">
|
||||||
|
<img class="w-full hidden dark:inline" src="/images/nadjas-nachtfoto-xxl.jpg"
|
||||||
|
src-set="nadjas-nachtfoto-xxl.jpg 1280w, /images/nadjas-nachtfoto-md.jpg 1199w, /images/nadjas-nachtfoto-sm.jpg 991w, /images/nadjas-nachtfoto-xs.jpg 767w">
|
||||||
</a>
|
</a>
|
||||||
<.dark_mode_widget />
|
<.dark_mode_widget />
|
||||||
</header>
|
</header>
|
||||||
<main class="px-4 py-20 sm:px-6 lg:px-8">
|
<main class="px-2 sm:px-6 lg:px-8">
|
||||||
<div class="mx-auto max-w-4xl">
|
<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} />
|
||||||
<%= @inner_content %>
|
<%= @inner_content %>
|
||||||
|
|||||||
@ -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 %>
|
||||||
|
|||||||
@ -3,7 +3,9 @@ defmodule OutlookWeb.PublicComponents do
|
|||||||
Provides components for showing and listing artikel and autoren.
|
Provides components for showing and listing artikel and autoren.
|
||||||
"""
|
"""
|
||||||
use Phoenix.Component
|
use Phoenix.Component
|
||||||
import Phoenix.HTML
|
import OutlookWeb.ViewHelpers
|
||||||
|
|
||||||
|
use OutlookWeb, :verified_routes
|
||||||
|
|
||||||
alias Phoenix.LiveView.JS
|
alias Phoenix.LiveView.JS
|
||||||
|
|
||||||
@ -11,26 +13,26 @@ defmodule OutlookWeb.PublicComponents do
|
|||||||
|
|
||||||
def autor(assigns) do
|
def autor(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<a href={"/autoren/#{@autor.id}"}>
|
<a href={~p"/autoren/#{@autor}"}>
|
||||||
<div class="p-4 my-2 border rounded-lg border-slate-400 text-slate-800">
|
<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="font-bold"><%= @autor.name %></div>
|
||||||
<div class=""><%= @autor.description %></div>
|
<div class=""><%= @autor.description |> tidy_raw %></div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
attr :artikel, :any, required: true
|
attr :artikel, :any, required: true
|
||||||
attr :show_author, :boolean, default: true
|
attr :show_autor, :boolean, default: true
|
||||||
|
|
||||||
def artikel(assigns) do
|
def artikel(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<.link navigate={"/artikel/#{@artikel.id}"}>
|
<.link navigate={~p"/artikel/#{@artikel}"}>
|
||||||
<div class="my-2 px-2 rounded border-2 border-solid border-gray-300">
|
<div class="my-2 px-2 rounded border-2 border-solid border-gray-300 dark:border-stone-800">
|
||||||
<h4 class="font-bold py-2"><%= @artikel.title %></h4>
|
<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><small><%= @artikel.date |> Calendar.strftime("%d.%m.%Y") %></small></div>
|
||||||
<div><%= @artikel.teaser |> raw %></div>
|
<div><%= @artikel.teaser |> tidy_raw %></div>
|
||||||
</div>
|
</div>
|
||||||
</.link>
|
</.link>
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -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 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,15 +1,21 @@
|
|||||||
defmodule OutlookWeb.ArtikelController do
|
defmodule OutlookWeb.ArtikelController do
|
||||||
use OutlookWeb, :controller
|
use OutlookWeb, :controller
|
||||||
|
|
||||||
alias Outlook.Artikel
|
alias Outlook.Public
|
||||||
|
|
||||||
def index(conn, _params) do
|
def index(conn, _params) do
|
||||||
artikel = Artikel.list_artikel()
|
artikel = Public.list_artikel()
|
||||||
render(conn, :index, artikel: artikel, page_title: "Artikel")
|
render(conn, :index, artikel: artikel, page_title: "Artikel")
|
||||||
end
|
end
|
||||||
|
|
||||||
def show(conn, %{"id" => id}) do
|
def show(conn, %{"tid" => tid} = params) do
|
||||||
artikel = Artikel.get_artikel!(id)
|
case Public.get_artikel_by_tid(tid) do
|
||||||
render(conn, :show, artikel: artikel, page_title: artikel.title)
|
{:ok, artikel} -> render(conn, :show, artikel: artikel, page_title: artikel.title)
|
||||||
|
{:error, message} -> conn
|
||||||
|
|> put_status(404)
|
||||||
|
|> put_view(OutlookWeb.ErrorHTML)
|
||||||
|
|> render("404.html")
|
||||||
|
|> halt()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
<header class="mb-20">
|
<header class="mb-6">
|
||||||
<h1 class="text-lg font-semibold leading-8 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={"/autoren/#{@artikel.article.author.id}"}><%= @artikel.article.author.name %></.link>
|
<p class="my-2"><.link href={~p"/autoren/#{@artikel.autor_id}"}><%= @artikel.autor_name %></.link>
|
||||||
— <%= Calendar.strftime(@artikel.article.date, "%d.%m.%Y") %></p>
|
— <%= Calendar.strftime(@artikel.date_org, "%d.%m.%Y") %></p>
|
||||||
<div>Original
|
<div>Original Artikel:
|
||||||
<.link href={@artikel.article.url} >
|
<.link class="hover:text-sky-700" href={@artikel.url_org} >
|
||||||
<%= @artikel.article.title %>
|
<%= @artikel.title_org %>
|
||||||
</.link><br>
|
</.link><br>
|
||||||
</div>
|
</div>
|
||||||
<div class="my-2">
|
<div class="my-2">
|
||||||
Übersetzung <%= Calendar.strftime(@artikel.date, "%d.%m.%Y") %>
|
Übersetzung: <%= Calendar.strftime(@artikel.date, "%d.%m.%Y") %>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="article w-full"><%= @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.article.author}"}>Back to Autor</.back>
|
<.back navigate={~p"/autoren/#{@artikel.autor_id}"}>Back to Autor</.back>
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
defmodule OutlookWeb.AutorController do
|
defmodule OutlookWeb.AutorController do
|
||||||
use OutlookWeb, :controller
|
use OutlookWeb, :controller
|
||||||
|
|
||||||
alias Outlook.Autoren
|
alias Outlook.Public
|
||||||
|
|
||||||
def index(conn, _params) do
|
def index(conn, _params) do
|
||||||
autoren = Autoren.list_autoren()
|
autoren = Public.list_autoren()
|
||||||
render(conn, :index, autoren: autoren, page_title: "Autoren")
|
render(conn, :index, autoren: autoren, page_title: "Autoren")
|
||||||
end
|
end
|
||||||
|
|
||||||
def show(conn, %{"id" => id}) do
|
def show(conn, %{"id" => id}) do
|
||||||
autor = Autoren.get_autor!(id)
|
autor = Public.get_autor!(id)
|
||||||
# artikel = Autoren.list_artikel(autor)
|
# artikel = Autoren.list_artikel(autor)
|
||||||
render(conn, :show, autor: autor, page_title: autor.name)
|
render(conn, :show, autor: autor, page_title: autor.name)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,13 +1,9 @@
|
|||||||
<.header>
|
<.header>
|
||||||
<%= @autor.name %>
|
<%= @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>
|
<: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_author={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
@ -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
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
defmodule OutlookWeb.ArticleLive.Show do
|
defmodule OutlookWeb.ArticleLive.Show do
|
||||||
use OutlookWeb, :live_view
|
use OutlookWeb, :live_view
|
||||||
|
|
||||||
alias Outlook.Articles
|
alias Outlook.{Articles,InternalTree,Translations}
|
||||||
alias Outlook.InternalTree
|
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(_params, _session, socket) do
|
def mount(_params, _session, socket) do
|
||||||
@ -11,12 +10,24 @@ defmodule OutlookWeb.ArticleLive.Show do
|
|||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_params(%{"id" => id}, _, socket) do
|
def handle_params(%{"id" => id}, _, socket) do
|
||||||
|
{:noreply, socket
|
||||||
|
|> assign(:page_title, page_title(socket.assigns.live_action))
|
||||||
|
|> get_and_assign_article(id)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("delete_translation", %{"id" => id}, socket) do
|
||||||
|
translation = Translations.get_translation!(id)
|
||||||
|
{:ok, _} = Translations.delete_translation(translation)
|
||||||
|
|
||||||
|
{:noreply, socket |> get_and_assign_article(socket.assigns.article.id)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_and_assign_article(socket, id) do
|
||||||
article = Articles.get_article_with_translations!(id)
|
article = Articles.get_article_with_translations!(id)
|
||||||
{:noreply,
|
socket
|
||||||
socket
|
|> assign(:article_content, InternalTree.garnish(article.content, %{tunits: %{class: "tunit"}}))
|
||||||
|> assign(:page_title, page_title(socket.assigns.live_action))
|
|> assign(:article, article)
|
||||||
|> assign(:article_content, InternalTree.garnish(article.content, %{tunits: %{class: "tunit"}}))
|
|
||||||
|> assign(:article, article)}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp page_title(:show), do: "Show Article"
|
defp page_title(:show), do: "Show Article"
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
<.table id="translations" rows={@article.translations} row_click={&JS.navigate(~p"/translations/#{&1}")}>
|
<.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="Language"><%= translation.language %></:col>
|
||||||
<:col :let={translation} label="Title"><%= translation.title %></:col>
|
<:col :let={translation} label="Title"><%= translation.title %></:col>
|
||||||
<:col :let={translation} label="Teaser"><%= translation.teaser %></:col>
|
<:col :let={translation} label="Teaser"><%= translation.teaser |> tidy_raw %></:col>
|
||||||
<:col :let={translation} label="Date"><%= translation.date %></:col>
|
<:col :let={translation} label="Date"><%= translation.date %></:col>
|
||||||
<:col :let={translation} label="Public"><%= translation.public %></:col>
|
<:col :let={translation} label="Public"><%= translation.public %></:col>
|
||||||
<:action :let={translation}>
|
<:action :let={translation}>
|
||||||
@ -28,11 +28,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<.link navigate={~p"/translations/#{translation}/edit"}>Edit</.link>
|
<.link navigate={~p"/translations/#{translation}/edit"}>Edit</.link>
|
||||||
</:action>
|
</:action>
|
||||||
<%!-- <:action :let={translation}>
|
<:action :let={translation}>
|
||||||
<.link phx-click={JS.push("delete", value: %{id: translation.id})} data-confirm="Are you sure?">
|
<.link phx-click={JS.push("delete_translation", value: %{id: translation.id})} data-confirm="Are you sure?">
|
||||||
Delete
|
Delete
|
||||||
</.link>
|
</.link>
|
||||||
</:action> --%>
|
</:action>
|
||||||
</.table>
|
</.table>
|
||||||
|
|
||||||
<div class="article">
|
<div class="article">
|
||||||
@ -40,10 +40,15 @@
|
|||||||
<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" />
|
||||||
|
|
||||||
<.link 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>
|
||||||
|
|
||||||
<.back navigate={~p"/articles"}>Back to articles</.back>
|
<.back navigate={~p"/authors/#{@article.author}"}>Back to author</.back>
|
||||||
|
|
||||||
<.modal :if={@live_action == :edit} id="article-modal" show on_cancel={JS.patch(~p"/articles/#{@article}")}>
|
<.modal :if={@live_action == :edit} id="article-modal" show on_cancel={JS.patch(~p"/articles/#{@article}")}>
|
||||||
<.live_component
|
<.live_component
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<.table id="authors" rows={@authors} row_click={&JS.navigate(~p"/authors/#{&1}")}>
|
<.table id="authors" rows={@authors} row_click={&JS.navigate(~p"/authors/#{&1}")}>
|
||||||
<:col :let={author} label="Name"><%= author.name %></:col>
|
<:col :let={author} label="Name"><%= author.name %></:col>
|
||||||
<:col :let={author} label="Description"><%= author.description %></:col>
|
<:col :let={author} label="Description"><%= author.description |> tidy_raw %></:col>
|
||||||
<:col :let={author} label="Homepage name"><%= author.homepage_name %></:col>
|
<:col :let={author} label="Homepage name"><%= author.homepage_name %></:col>
|
||||||
<:col :let={author} label="Homepage url"><%= author.homepage_url %></:col>
|
<:col :let={author} label="Homepage url"><%= author.homepage_url %></:col>
|
||||||
<:action :let={author}>
|
<:action :let={author}>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
defmodule OutlookWeb.AuthorLive.Show do
|
defmodule OutlookWeb.AuthorLive.Show do
|
||||||
use OutlookWeb, :live_view
|
use OutlookWeb, :live_view
|
||||||
|
|
||||||
alias Outlook.Authors
|
alias Outlook.{Authors,Articles}
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(_params, _session, socket) do
|
def mount(_params, _session, socket) do
|
||||||
@ -16,6 +16,15 @@ defmodule OutlookWeb.AuthorLive.Show do
|
|||||||
|> assign(:author, Authors.get_author_with_articles!(id))}
|
|> assign(:author, Authors.get_author_with_articles!(id))}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("delete_article", %{"id" => id}, socket) do
|
||||||
|
article = Articles.get_article!(id)
|
||||||
|
{:ok, _} = Articles.delete_article(article)
|
||||||
|
|
||||||
|
{:noreply, socket
|
||||||
|
|> assign(:author, Authors.get_author_with_articles!(socket.assigns.author.id))}
|
||||||
|
end
|
||||||
|
|
||||||
defp page_title(:show), do: "Show Author"
|
defp page_title(:show), do: "Show Author"
|
||||||
defp page_title(:edit), do: "Edit Author"
|
defp page_title(:edit), do: "Edit Author"
|
||||||
end
|
end
|
||||||
|
|||||||
@ -31,7 +31,7 @@
|
|||||||
<.link patch={~p"/articles/#{article}/edit"}>Edit</.link>
|
<.link patch={~p"/articles/#{article}/edit"}>Edit</.link>
|
||||||
</:action>
|
</:action>
|
||||||
<:action :let={article}>
|
<:action :let={article}>
|
||||||
<.link phx-click={JS.push("delete", value: %{id: article.id})} data-confirm="Are you sure?">
|
<.link phx-click={JS.push("delete_article", value: %{id: article.id})} data-confirm="Are you sure?">
|
||||||
Delete
|
Delete
|
||||||
</.link>
|
</.link>
|
||||||
</:action>
|
</:action>
|
||||||
|
|||||||
@ -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" />
|
||||||
<.input field={{f, :language}} type="select" label="language"
|
<div class="more-fields">
|
||||||
options={Application.get_env(:outlook,:deepl)[:target_langs]} />
|
<.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" />
|
<.input field={{f, :title}} type="text" label="title" />
|
||||||
<.input field={{f, :teaser}} type="textarea" label="teaser" class="h-28" />
|
<div class="more-fields">
|
||||||
<.input field={{f, :date}} type="datetime-local" label="date" />
|
<.input field={{f, :teaser}} type="textarea" label="teaser" class="h-28" />
|
||||||
<div class="flex items-center justify-between">
|
<.input field={{f, :date}} type="datetime-local" label="date" />
|
||||||
<.input field={{f, :public}} type="checkbox" label="public" />
|
<div class="flex items-center justify-between">
|
||||||
<.input field={{f, :unauthorized}} type="checkbox" label="unauthorized" />
|
<.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>
|
</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)}
|
||||||
socket
|
end
|
||||||
|> update_translation_with_current_tunit(socket.assigns.current_tunit.status)
|
|
||||||
|> assign(:current_tunit, socket.assigns.translation_content[nid])}
|
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])
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "updating on browser events"
|
@doc "updating on browser events"
|
||||||
@ -146,8 +175,9 @@ 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"}}))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp update_translation_with_current_tunit(socket, nil), do: socket
|
defp update_translation_with_current_tunit(socket, nil), do: socket
|
||||||
@ -186,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))
|
||||||
|
|||||||
@ -2,10 +2,10 @@
|
|||||||
Listing Translations
|
Listing Translations
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
<.table id="translations" rows={@translations} row_click={&JS.navigate(~p"/translations/#{&1}")}>
|
<.table id="translations" rows={@translations} row_click={&JS.navigate(~p(/translations/#{&1}))}>
|
||||||
<:col :let={translation} label="Language"><%= translation.language %></:col>
|
<:col :let={translation} label="Language"><%= translation.language %></:col>
|
||||||
<:col :let={translation} label="Title"><%= translation.title %></:col>
|
<:col :let={translation} label="Title"><%= translation.title %></:col>
|
||||||
<:col :let={translation} label="Teaser"><%= translation.teaser %></:col>
|
<:col :let={translation} label="Teaser"><%= translation.teaser |> tidy_raw %></:col>
|
||||||
<%!-- <:col :let={translation} label="Content"><%= translation.content %></:col> --%>
|
<%!-- <:col :let={translation} label="Content"><%= translation.content %></:col> --%>
|
||||||
<:col :let={translation} label="Date"><%= translation.date %></:col>
|
<:col :let={translation} label="Date"><%= translation.date %></:col>
|
||||||
<:col :let={translation} label="Public"><%= translation.public %></:col>
|
<:col :let={translation} label="Public"><%= translation.public %></:col>
|
||||||
@ -22,19 +22,3 @@
|
|||||||
</.link>
|
</.link>
|
||||||
</:action>
|
</:action>
|
||||||
</.table>
|
</.table>
|
||||||
|
|
||||||
<.modal
|
|
||||||
:if={@live_action in [:new, :edit]}
|
|
||||||
id="translation-modal"
|
|
||||||
show
|
|
||||||
on_cancel={JS.navigate(~p"/translations")}
|
|
||||||
>
|
|
||||||
<.live_component
|
|
||||||
module={OutlookWeb.TranslationLive.FormComponent}
|
|
||||||
id={@translation.id || :new}
|
|
||||||
title={@page_title}
|
|
||||||
action={@live_action}
|
|
||||||
translation={@translation}
|
|
||||||
navigate={~p"/translations"}
|
|
||||||
/>
|
|
||||||
</.modal>
|
|
||||||
|
|||||||
@ -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"/translations"}>Back to translations</.back>
|
<.back navigate={~p"/articles/#{@translation.article}"}>Back to <article></article></.back>
|
||||||
|
|||||||
@ -34,7 +34,7 @@ defmodule OutlookWeb.Router do
|
|||||||
get "/", ArtikelController, :index
|
get "/", ArtikelController, :index
|
||||||
|
|
||||||
resources "/autoren", AutorController, only: [:index, :show]
|
resources "/autoren", AutorController, only: [:index, :show]
|
||||||
resources "/artikel", ArtikelController, only: [:index, :show]
|
resources "/artikel", ArtikelController, only: [:index, :show], param: "tid"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Other scopes may use custom stacks.
|
# Other scopes may use custom stacks.
|
||||||
|
|||||||
30
lib/outlook_web/view_helpers.ex
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
defmodule OutlookWeb.ViewHelpers do
|
||||||
|
|
||||||
|
import Phoenix.HTML, only: [raw: 1]
|
||||||
|
|
||||||
|
@doc "Just sanitize tags"
|
||||||
|
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
|
||||||
20
mix.lock
@ -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": {: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_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"},
|
"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.0", "9ba6ead054abd43cb3d7b14946a0cdd1493698ccd8e054e0e5d6286d7f0f509c", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "30f9a05d4a5bab0d3e37398f312f80864e1ee1a081ca09149d06d474318fd040"},
|
"esbuild": {:hex, :esbuild, "0.6.1", "a774bfa7b4512a1211bf15880b462be12a4c48ed753a170c68c63b2c95888150", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "569f7409fb5a932211573fc20e2a930a0d5cf3377c5b4f6506c651b1783a1678"},
|
||||||
"expo": {:hex, :expo, "0.1.0", "d4e932bdad052c374118e312e35280f1919ac13881cb3ac07a209a54d0c81dd8", [:mix], [], "hexpm", "c22c536021c56de058aaeedeabb4744eb5d48137bacf8c29f04d25b6c6bbbf45"},
|
"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"},
|
"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"},
|
"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"},
|
"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.0", "002d0cc194b48794d74711731db004fafeb328fe676976f160685262d43706a8", [:mix], [], "hexpm", "9c3a9f43f40dde00332a589bd9d389b90c1f518aef500364d00636acc5ebc99c"},
|
"floki": {:hex, :floki, "0.34.2", "5fad07ef153b3b8ec110b6b155ec3780c4b2c4906297d0b4be1a7162d04a7e02", [:mix], [], "hexpm", "26b9d50f0f01796bc6be611ca815c5e0de034d2128e39cc9702eee6b66a4d1c8"},
|
||||||
"gettext": {:hex, :gettext, "0.21.0", "15bbceb20b317b706a8041061a08e858b5a189654128618b53746bf36c84352b", [:mix], [{:expo, "~> 0.1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "04a66db4103b6d1d18f92240bb2c73167b517229316b7bef84e4eebbfb2f14f6"},
|
"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"},
|
"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"},
|
"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"},
|
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
|
||||||
@ -34,14 +34,14 @@
|
|||||||
"nimble_options": {:hex, :nimble_options, "0.5.2", "42703307b924880f8c08d97719da7472673391905f528259915782bb346e0a1b", [:mix], [], "hexpm", "4da7f904b915fd71db549bcdc25f8d56f378ef7ae07dc1d372cbe72ba950dce0"},
|
"nimble_options": {:hex, :nimble_options, "0.5.2", "42703307b924880f8c08d97719da7472673391905f528259915782bb346e0a1b", [:mix], [], "hexpm", "4da7f904b915fd71db549bcdc25f8d56f378ef7ae07dc1d372cbe72ba950dce0"},
|
||||||
"nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"},
|
"nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"},
|
||||||
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
|
"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_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.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"},
|
"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_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_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.7", "2b5239bb3d7ceead2b3dc368a1dc588a91e55df46c4222441609018b2df151b8", [: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.1", [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", "7027a71a3c94c3e2088d401f873f7a7adb045eea6c9493af1934389e30458e87"},
|
"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_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
|
||||||
"phoenix_template": {:hex, :phoenix_template, "1.0.0", "c57bc5044f25f007dc86ab21895688c098a9f846a8dda6bc40e2d0ddc146e38f", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "1b066f99a26fd22064c12b2600a9a6e56700f591bf7b20b418054ea38b4d4357"},
|
"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"},
|
"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"},
|
||||||
"plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"},
|
"plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"},
|
||||||
"plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"},
|
"plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"},
|
||||||
@ -50,7 +50,7 @@
|
|||||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
|
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
|
||||||
"swoosh": {:hex, :swoosh, "1.9.1", "0a5d7bf9954eb41d7e55525bc0940379982b090abbaef67cd8e1fd2ed7f8ca1a", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "76dffff3ffcab80f249d5937a592eaef7cc49ac6f4cdd27e622868326ed6371e"},
|
"swoosh": {:hex, :swoosh, "1.9.1", "0a5d7bf9954eb41d7e55525bc0940379982b090abbaef67cd8e1fd2ed7f8ca1a", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "76dffff3ffcab80f249d5937a592eaef7cc49ac6f4cdd27e622868326ed6371e"},
|
||||||
"table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"},
|
"table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"},
|
||||||
"tailwind": {:hex, :tailwind, "0.1.9", "25ba09d42f7bfabe170eb67683a76d6ec2061952dc9bd263a52a99ba3d24bd4d", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "9213f87709c458aaec313bb5f2df2b4d2cedc2b630e4ae821bf3c54c47a56d0b"},
|
"tailwind": {:hex, :tailwind, "0.1.10", "21ed80ae1f411f747ee513470578acaaa1d0eb40170005350c5b0b6d07e2d624", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "e0fc474dfa8ed7a4573851ac69c5fd3ca70fbb0a5bada574d1d657ebc6f2f1f1"},
|
||||||
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
||||||
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
|
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
|
||||||
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
|
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
|
||||||
|
|||||||
@ -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
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 37 KiB |
BIN
priv/static/images/nadjas-nachtfoto-lg.jpg
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
priv/static/images/nadjas-nachtfoto-md.jpg
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
priv/static/images/nadjas-nachtfoto-sm.jpg
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
priv/static/images/nadjas-nachtfoto-xs.jpg
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
priv/static/images/nadjas-nachtfoto-xxl.jpg
Normal file
|
After Width: | Height: | Size: 40 KiB |
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
@ -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
@ -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
@ -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
@ -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()
|
||||||
|
|
||||||
|
|||||||