Skip to content

Commit 72082ef

Browse files
committed
Fixes
1 parent 04d1cf4 commit 72082ef

File tree

8 files changed

+125
-87
lines changed

8 files changed

+125
-87
lines changed

lib/diff_web/live/components/diff_component.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ defmodule DiffWeb.DiffComponent do
7676
defp line_id(diff, line) do
7777
hash = :erlang.phash2({diff.from, diff.to})
7878
ln = "-#{line.from_line_number}-#{line.to_line_number}"
79-
[to_string(hash), ln]
79+
"#{hash}#{ln}"
8080
end
8181

8282
defp line_type(line), do: to_string(line.type)

lib/diff_web/live/diff_live_view.ex

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ defmodule DiffWeb.DiffLiveView do
227227

228228
diff_data =
229229
Jason.encode!(%{
230-
"diff" => DiffWeb.LiveView.sanitize_utf8(raw_diff),
230+
"diff" => sanitize_utf8(raw_diff),
231231
"path_from" => path_from,
232232
"path_to" => path_to
233233
})
@@ -336,4 +336,45 @@ defmodule DiffWeb.DiffLiveView do
336336
end
337337

338338
defp build_url(app, from, to), do: "/diff/#{app}/#{from}..#{to}"
339+
340+
# UTF-8 sanitization functions
341+
defp sanitize_utf8(content) when is_binary(content) do
342+
case String.valid?(content) do
343+
true ->
344+
content
345+
346+
false ->
347+
# Multiple fallback strategies for invalid UTF-8
348+
content
349+
|> sanitize_invalid_bytes()
350+
end
351+
end
352+
353+
defp sanitize_utf8(content), do: content
354+
355+
defp sanitize_invalid_bytes(content) do
356+
# Try different encoding conversions and fallbacks
357+
cond do
358+
# Try converting from Latin-1/ISO-8859-1 encoding
359+
latin1_result = safe_unicode_convert(content, :latin1, :utf8) ->
360+
latin1_result
361+
362+
# Last resort: replace invalid bytes with replacement character
363+
true ->
364+
content
365+
|> :binary.bin_to_list()
366+
# Replace high bytes with '?'
367+
|> Enum.map(fn byte -> if byte > 127, do: 63, else: byte end)
368+
|> :binary.list_to_bin()
369+
end
370+
end
371+
372+
defp safe_unicode_convert(content, from, to) do
373+
case :unicode.characters_to_binary(content, from, to) do
374+
result when is_binary(result) -> result
375+
_ -> nil
376+
end
377+
rescue
378+
_ -> nil
379+
end
339380
end

lib/diff_web/live/search_view.ex

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,60 @@ defmodule DiffWeb.SearchLiveView do
22
use DiffWeb, :live_view
33

44
def render(assigns) do
5-
DiffWeb.SearchView.render("search.html", assigns)
5+
~H"""
6+
<div class="search-area">
7+
<form phx-change="search" phx-submit="go" class="search-form">
8+
<input
9+
autocomplete="off"
10+
autofocus
11+
class="search-input"
12+
type="text"
13+
name="q"
14+
value={@query}
15+
placeholder="Search..."
16+
/>
17+
<div class="suggestions">
18+
<%= if length(@suggestions) > 0 do %>
19+
Did you mean:
20+
<%= for suggestion <- @suggestions do %>
21+
<span class="suggestion" phx-click={"search_#{suggestion}"} onclick=""><%= suggestion %></span>
22+
<% end %>
23+
<% end %>
24+
</div>
25+
</form>
26+
<%= if length(@releases) == 1 do %>
27+
The package only has one version so there's nothing to diff with.
28+
<% else %>
29+
<%= if @result do %>
30+
<form phx-change="select_version" class="version-form">
31+
<div class="version-area">
32+
<div class="select-area">
33+
<label for="from">From</label>
34+
<select name="from">
35+
<%= for release_from <- @from_releases do %>
36+
<option selected={selected(@from, release_from)} value={release_from}><%= release_from %></option>
37+
<% end %>
38+
</select>
39+
<label for="to">To</label>
40+
<select name="to">
41+
<%= for release_to <- @to_releases do %>
42+
<option selected={selected(@to, release_to)} value={release_to}><%= release_to %></option>
43+
<% end %>
44+
</select>
45+
</div>
46+
<button
47+
class="diff-button"
48+
type="button"
49+
disabled={disabled([@from, @to])}
50+
phx-click="go"
51+
>Diff</button>
52+
</div>
53+
</form>
54+
<% end %>
55+
<% end %>
56+
<%= @not_found %>
57+
</div>
58+
"""
659
end
760

861
def mount(_params, _session, socket) do
@@ -110,6 +163,16 @@ defmodule DiffWeb.SearchLiveView do
110163

111164
defp build_url(app, from, to), do: "/diff/#{app}/#{from}..#{to}"
112165

166+
# Helper functions for template
167+
defp disabled(things) when is_list(things) do
168+
Enum.any?(things, &(!&1))
169+
end
170+
171+
defp disabled(thing), do: disabled([thing])
172+
173+
defp selected(x, x), do: true
174+
defp selected(_, _), do: false
175+
113176
defp get_suggestions(query, number) do
114177
package_names = Diff.Package.Store.get_names()
115178
starts_with = package_starts_with(package_names, query)

lib/diff_web/templates/search/search.html.leex

Lines changed: 0 additions & 54 deletions
This file was deleted.

lib/diff_web/views/live_view.ex

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,8 @@ defmodule DiffWeb.LiveView do
88
# Parse the stored content based on format
99
case Jason.decode(raw_content) do
1010
{:ok, %{"type" => "too_large", "file" => file_path}} ->
11-
rendered = DiffWeb.TooLargeComponent.render(%{file: file_path})
12-
13-
rendered.static
14-
|> Enum.zip_with(rendered.dynamic.(true), fn static, dynamic -> [static, dynamic] end)
11+
DiffWeb.TooLargeComponent.render(%{file: file_path})
12+
|> Phoenix.HTML.Safe.to_iodata()
1513
|> IO.iodata_to_binary()
1614
|> sanitize_utf8()
1715

@@ -24,12 +22,8 @@ defmodule DiffWeb.LiveView do
2422
# Take the first diff (should only be one per file)
2523
diff = List.first(diffs)
2624

27-
rendered = DiffWeb.DiffComponent.render(%{diff: diff})
28-
29-
rendered.static
30-
|> Enum.zip_with(rendered.dynamic.(true), fn static, dynamic ->
31-
[static, dynamic]
32-
end)
25+
DiffWeb.DiffComponent.render(%{diff: diff})
26+
|> Phoenix.HTML.Safe.to_iodata()
3327
|> IO.iodata_to_binary()
3428
|> sanitize_utf8()
3529

@@ -44,7 +38,7 @@ defmodule DiffWeb.LiveView do
4438
end
4539
end
4640

47-
def sanitize_utf8(content) when is_binary(content) do
41+
defp sanitize_utf8(content) when is_binary(content) do
4842
case String.valid?(content) do
4943
true ->
5044
content
@@ -55,7 +49,7 @@ defmodule DiffWeb.LiveView do
5549
end
5650
end
5751

58-
def sanitize_utf8(content), do: content
52+
defp sanitize_utf8(content), do: content
5953

6054
defp sanitize_invalid_bytes(content) do
6155
# Try different encoding conversions and fallbacks

lib/diff_web/views/search_view.ex

Lines changed: 0 additions & 16 deletions
This file was deleted.

test/diff/storage/local_test.exs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ defmodule Diff.Storage.LocalTest do
4545
assert :ok = Local.put_diff("phoenix", "1.4.5", "1.4.9", diff_id, content)
4646
end
4747

48+
# Store metadata (required for list_diffs to work)
49+
metadata = %{
50+
total_diffs: 3,
51+
total_additions: 10,
52+
total_deletions: 5,
53+
files_changed: 3
54+
}
55+
56+
assert :ok = Local.put_metadata("phoenix", "1.4.5", "1.4.9", metadata)
57+
4858
# List diffs
4959
assert {:ok, diff_ids} = Local.list_diffs("phoenix", "1.4.5", "1.4.9")
5060
assert Enum.sort(diff_ids) == ["diff-0", "diff-1", "diff-2"]

test/diff_web/live/search_view_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ defmodule DiffWeb.SearchLiveViewTest do
4242
send(view.pid, {:search, "phoenix"})
4343
rendered = render(view)
4444
assert rendered =~ ~s(<select name="from")
45-
assert rendered =~ ~s(<option selected="selected" value="1.4.10">1.4.10</option>)
45+
assert rendered =~ ~s(<option selected="" value="1.4.10">1.4.10</option>)
4646
assert rendered =~ ~s(<select name="to")
47-
assert rendered =~ ~s(<option selected="selected" value="1.4.11">1.4.11</option>)
47+
assert rendered =~ ~s(<option selected="" value="1.4.11">1.4.11</option>)
4848
assert rendered =~ ~s(Did you mean:)
4949

5050
assert rendered =~

0 commit comments

Comments
 (0)