Compare commits

10 Commits

Author SHA1 Message Date
2111cfa1d1 Add iframe to elements that may be empty 2025-05-05 14:45:43 +02:00
c8d1639afa Fix ugly bug 2023-06-09 13:57:05 +02:00
43e11178db Fix darkmode-widget cover buttons 2023-06-08 16:33:03 +02:00
4828a68bcd Fix following links when editing translation 2023-05-29 13:19:42 +02:00
2b25c13095 Fix bug which omits element's attributes
There is a questionable design decision: for
getting the attributes from the %InternalNode{} into
the rendered html output they always have to be copied
into node.eph.attributes. (OutlookWeb.HtmlDocComponent.dnode())
2023-05-28 15:31:08 +02:00
357bcae450 Add ’ to partitioning regex 2023-05-28 15:23:25 +02:00
698da7f8b7 Add toggling fields in translation form 2023-05-04 18:14:44 +02:00
d53eceb7a0 Add remarks to articles 2023-05-04 18:12:57 +02:00
888e7575f0 Add remarks field to translations 2023-05-04 18:11:40 +02:00
8a0e2f22c1 Improve html tree component 2023-05-03 22:37:43 +02:00
17 changed files with 69 additions and 20 deletions

View File

@ -6,6 +6,8 @@ let TranslationFormHook = {
this.tunit_editor = this.el.querySelector("#tunit-editor-content") this.tunit_editor = this.el.querySelector("#tunit-editor-content")
this.save_edit_button = this.el.querySelector("#save-edit-button") this.save_edit_button = this.el.querySelector("#save-edit-button")
this.save_publish_button = this.el.querySelector("#save-publish-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) { keyupHandler(e) {
var push_event = true var push_event = true

View File

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

View File

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

View File

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

View File

@ -52,7 +52,7 @@ defmodule Outlook.InternalTree.TunitModifications do
end end
[%InternalNode{node| content: content} | apply_modifier(rest, modifier, tu_ids)] [%InternalNode{node| content: content} | apply_modifier(rest, modifier, tu_ids)]
end end
def apply_modifier([node, rest], modifier, tu_ids), do: [node | apply_modifier(rest, modifier, tu_ids)] def apply_modifier([node | rest], modifier, tu_ids), do: [node | apply_modifier(rest, modifier, tu_ids)]
def apply_modifier([],_, _), do: [] def apply_modifier([],_, _), do: []
def process_tunit_list(tunits, modifier, tu_ids) do def process_tunit_list(tunits, modifier, tu_ids) do

View File

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

View File

@ -9,7 +9,7 @@ defmodule OutlookWeb.DarkModeComponent do
def dark_mode_widget(assigns) do def dark_mode_widget(assigns) do
~H""" ~H"""
<div id="dark-mode-widget" class="absolute flex w-full justify-between p-0" phx-hook="dark_mode_widget"> <div id="dark-mode-widget" class="relative flex w-full justify-between p-0" phx-hook="dark_mode_widget">
<div class="flex"></div> <div class="flex"></div>
<div class="flex"> <div class="flex">
<button class="p-2" type="button"> <button class="p-2" type="button">

View File

@ -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"&nbsp; <%= @name %>=&quot;<%= @value %>&quot;" ~H"&nbsp; <%= @name %>=&quot;<%= elipsed_text(@value, 16) %>&quot;"
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

View File

@ -8,7 +8,7 @@
</a> </a>
<.dark_mode_widget /> <.dark_mode_widget />
</header> </header>
<main class="px-2 py-4 sm:px-6 lg:px-8"> <main class="px-2 sm:px-6 lg:px-8">
<div class="mx-auto max-w-xl"> <div class="mx-auto max-w-xl">
<.flash kind={:info} title="Success!" flash={@flash} /> <.flash kind={:info} title="Success!" flash={@flash} />
<.flash kind={:error} title="Error!" flash={@flash} /> <.flash kind={:error} title="Error!" flash={@flash} />

View File

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

View File

@ -45,7 +45,7 @@ defmodule OutlookWeb.ArticleLive.NewComponents do
~H""" ~H"""
<div>Review Translation Units</div> <div>Review Translation Units</div>
<div class="flex gap-4"> <div class="flex gap-4">
<div id="html-tree" class="w-96 overflow-auto whitespace-nowrap"> <div id="html-tree" class="article w-96 overflow-auto whitespace-nowrap">
<.render_tree tree={@raw_internal_tree} ></.render_tree> <.render_tree tree={@raw_internal_tree} ></.render_tree>
</div> </div>
<div id="partition-preview" class="article show-boundary overflow-auto h-full"> <div id="partition-preview" class="article show-boundary overflow-auto h-full">

View File

@ -40,6 +40,10 @@
<a href="#" class="hide-link" phx-click={JS.remove_class("show-boundary", to: ".article")}>hide boundaries</a> <a href="#" class="hide-link" phx-click={JS.remove_class("show-boundary", to: ".article")}>hide boundaries</a>
<.render_doc tree={@article_content} /> <.render_doc tree={@article_content} />
</div> </div>
<div class="my-4">
<div>Remarks:</div>
<%= @article.remarks |> tidy_raw %>
</div>
<div class="h-10" /> <div class="h-10" />
<.link class="text-sm font-semibold" navigate={~p"/translations/new?article_id=#{@article.id}"}>New Translation</.link> <.link class="text-sm font-semibold" navigate={~p"/translations/new?article_id=#{@article.id}"}>New Translation</.link>

View File

@ -14,6 +14,7 @@ defmodule OutlookWeb.TranslationLive.FormComponent do
<: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,14 +24,19 @@ 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" />
@ -45,7 +51,7 @@ defmodule OutlookWeb.TranslationLive.FormComponent do
</.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} />

View File

@ -11,11 +11,12 @@
<.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 show_status"> <div class="article show_status">

View File

@ -18,4 +18,13 @@ defmodule OutlookWeb.ViewHelpers do
def strip_links(html) do def strip_links(html) do
raise "Yet to be implemented!" raise "Yet to be implemented!"
end end
def elipsed_text(text, length) do
if String.length(text) < length do
text
else
part_length = (length - 3) / 2 |> trunc()
"#{String.slice(text, 0..part_length)}#{String.slice(text, -part_length..-1)}"
end
end
end end

View File

@ -0,0 +1,9 @@
defmodule Outlook.Repo.Migrations.AddRemarksToTranslations do
use Ecto.Migration
def change do
alter table(:translations) do
add :remarks, :text
end
end
end

View File

@ -0,0 +1,9 @@
defmodule Outlook.Repo.Migrations.AddRemarksToArticles do
use Ecto.Migration
def change do
alter table(:articles) do
add :remarks, :text
end
end
end