Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .formatter.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[
import_deps: [:ecto, :phoenix],
plugins: [Phoenix.LiveView.HTMLFormatter, TailwindFormatter.MultiFormatter, DoctestFormatter],
plugins: [TailwindFormatter, DoctestFormatter, Phoenix.LiveView.HTMLFormatter],
heex_line_length: 300,
inputs: [
"*.{heex,ex,exs}",
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/style.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ jobs:

strategy:
matrix:
otp: [26.x]
elixir: [1.14.x]
otp: [27.x]
elixir: [1.17.x]

steps:
- name: ☁️ Checkout repository
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ jobs:

strategy:
matrix:
otp: [26.x]
elixir: [1.14.x]
otp: [27.x]
elixir: [1.17.x]

services:
db:
Expand Down
4 changes: 2 additions & 2 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
elixir 1.14.4-otp-26
erlang 26.1.1
elixir 1.17.2-otp-27
erlang 27.0
10 changes: 4 additions & 6 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {LiveSocket} from "phoenix_live_view"
import "../vendor/alpine.js";
import topbar from "../vendor/topbar"
import { QrScanner, InitSorting, StickyScroll, ScrollToTop } from "./hooks";
import phxFeedbackDom from "./shims/phx_feedback_dom.js"

let Hooks = {
QrScanner: QrScanner,
Expand All @@ -37,16 +38,13 @@ let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("
let liveSocket = new LiveSocket("/live", Socket, {
params: { _csrf_token: csrfToken },
hooks: Hooks,
dom: {
dom: phxFeedbackDom({
onBeforeElUpdated(from, to) {
// If the element we are updating is an Alpine component...
if (from._x_dataStack) {
// Then temporarily clone it (with it's data) to the "to" element.
// This should simulate LiveView being aware of Alpine changes.
window.Alpine.clone(from, to);
}
},
},
}
}),
});

// Show progress bar on live navigation and form submits
Expand Down
61 changes: 61 additions & 0 deletions assets/js/shims/phx_feedback_dom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// https://gist.github.com/chrismccord/c4c60328c6ac5ec29e167bb115315d82
// maintain backwards compatibility of phx-feedback-for, which was removed in LiveView 1.0
// find all phx-feedback-for containers and show/hide phx-no-feedback class based on used inputs
import {isUsedInput} from "phoenix_live_view"

let resetFeedbacks = (container, feedbacks) => {
feedbacks = feedbacks || Array.from(container.querySelectorAll("[phx-feedback-for]"))
.map(el => [el, el.getAttribute("phx-feedback-for")])

feedbacks.forEach(([feedbackEl, name]) => {
let query = `[name="${name}"], [name="${name}[]"]`
let isUsed = Array.from(container.querySelectorAll(query)).find(input => isUsedInput(input))
if(isUsed || !feedbackEl.hasAttribute("phx-feedback-for")){
feedbackEl.classList.remove("phx-no-feedback")
} else {
feedbackEl.classList.add("phx-no-feedback")
}
})
}

export default phxFeedbackDom = (dom) => {
window.addEventListener("reset", e => resetFeedbacks(document))
let feedbacks
let submitPending = false
let inputPending = false
window.addEventListener("submit", e => submitPending = e.target)
window.addEventListener("input", e => inputPending = e.target)
// extend provided dom options with our own.
// accumulate phx-feedback-for containers for each patch and reset feedbacks when patch ends
return {
onPatchStart(container){
feedbacks = []
dom.onPatchStart && dom.onPatchStart(container)
},
onNodeAdded(node){
if(node.hasAttribute && node.hasAttribute("phx-feedback-for")){
feedbacks.push([node, node.getAttribute("phx-feedback-for")])
}
dom.onNodeAdded && dom.onNodeAdded(node)
},
onBeforeElUpdated(from, to){
let fromFor = from.getAttribute("phx-feedback-for")
let toFor = to.getAttribute("phx-feedback-for")
if(fromFor || toFor){ feedbacks.push([from, fromFor || toFor], [to, toFor || fromFor]) }

dom.onBeforeElUpdated && dom.onBeforeElUpdated(from, to)
},
onPatchEnd(container){
resetFeedbacks(container, feedbacks)
// we might not find some feedback nodes if they are skipped in the patch
// therefore we explicitly reset feedbacks for all nodes when the patch
// follows a submit or input event
if(inputPending || submitPending){
resetFeedbacks(container)
inputPending = null
submitPending = null
}
dom.onPatchEnd && dom.onPatchEnd(container)
}
}
}
4 changes: 4 additions & 0 deletions config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,9 @@ config :phoenix, :stacktrace_depth, 20
# Initialize plugs at runtime for faster development compilation
config :phoenix, :plug_init_mode, :runtime

config :phoenix_live_view,
# Include HEEx debug annotations as HTML comments in rendered markup
debug_heex_annotations: true

# Other configurations for the app
config :pdf_generator, raise_on_missing_wkhtmltopdf_binary: false
6 changes: 4 additions & 2 deletions lib/atomic/organizations/collaborator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ defmodule Atomic.Organizations.Collaborator do
order_by: [:inserted_at],
order_directions: [:desc]
},
join_fields: [
collaborator_name: [binding: :user, field: :name, path: [:user, :name]]
adapter_opts: [
join_fields: [
collaborator_name: [binding: :user, field: :name, path: [:user, :name]]
]
]
}

Expand Down
5 changes: 3 additions & 2 deletions lib/atomic_web.ex
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,9 @@ defmodule AtomicWeb do

defp view_helpers do
quote do
# Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML
import Phoenix.HTML
import Phoenix.HTML.Form
use PhoenixHTMLHelpers

# Import LiveView and .heex helpers (<.link>, <.form>, etc)
import Phoenix.LiveView.Helpers
Expand Down
14 changes: 7 additions & 7 deletions lib/atomic_web/components/activity.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,19 @@ defmodule AtomicWeb.Components.Activity do
<object>
<.link navigate={~p"/organizations/#{@activity.organization.id}"}>
<span class="text-sm font-medium text-zinc-900 hover:underline focus:outline-none">
<%= @activity.organization.name %>
{@activity.organization.name}
</span>
</.link>
</object>
<p class="text-sm text-zinc-500">
<span class="sr-only">Published on</span>
<time><%= relative_datetime(@activity.inserted_at) %></time>
<time>{relative_datetime(@activity.inserted_at)}</time>
</p>
</div>
</div>
<h2 class="mt-3 text-base font-semibold text-zinc-900"><%= @activity.title %></h2>
<h2 class="mt-3 text-base font-semibold text-zinc-900">{@activity.title}</h2>
<div class="overflow-hidden break-words text-justify text-sm text-zinc-700">
<p><%= maybe_slice_string(@activity.description, 300) %></p>
<p>{maybe_slice_string(@activity.description, 300)}</p>
</div>
<!-- Image -->
<%= if @activity.image do %>
Expand All @@ -47,21 +47,21 @@ defmodule AtomicWeb.Components.Activity do
<span class="inline-flex items-center text-sm">
<span class="inline-flex space-x-2 text-zinc-400">
<.icon name="hero-calendar-solid" class="mr-1.5 h-5 w-5 flex-shrink-0 text-zinc-400" />
<span class="font-medium text-zinc-900"><%= pretty_display_date(@activity.start) %></span>
<span class="font-medium text-zinc-900">{pretty_display_date(@activity.start)}</span>
<span class="sr-only">starting in</span>
</span>
</span>
<span class="inline-flex items-center text-sm">
<span class="inline-flex space-x-2 text-zinc-400">
<.icon name="hero-user-group-solid" class="size-5" />
<span class="font-medium text-zinc-900"><%= @activity.enrolled %>/<%= @activity.maximum_entries %></span>
<span class="font-medium text-zinc-900">{@activity.enrolled}/{@activity.maximum_entries}</span>
<span class="sr-only text-zinc-400">enrollments</span>
</span>
</span>
<span class="inline-flex items-center text-sm">
<span class="inline-flex space-x-2 text-zinc-400">
<.icon name="hero-map-pin-solid" class="size-5" />
<span class="font-medium text-zinc-900"><%= @activity.location.name %></span>
<span class="font-medium text-zinc-900">{@activity.location.name}</span>
<span class="sr-only">location</span>
</span>
</span>
Expand Down
8 changes: 4 additions & 4 deletions lib/atomic_web/components/announcement.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,19 @@ defmodule AtomicWeb.Components.Announcement do
<object>
<.link navigate={~p"/organizations/#{@announcement.organization.id}"} class="hover:underline focus:outline-none">
<p class="text-sm font-medium text-zinc-900">
<%= @announcement.organization.name %>
{@announcement.organization.name}
</p>
</.link>
</object>
<p class="text-sm text-zinc-500">
<span class="sr-only">Published on</span>
<time><%= relative_datetime(@announcement.inserted_at) %></time>
<time>{relative_datetime(@announcement.inserted_at)}</time>
</p>
</div>
</div>
<h2 class="mt-3 text-base font-semibold text-zinc-900"><%= @announcement.title %></h2>
<h2 class="mt-3 text-base font-semibold text-zinc-900">{@announcement.title}</h2>
<div class="space-y-4 overflow-hidden break-words text-justify text-sm text-zinc-700">
<%= maybe_slice_string(@announcement.description, 300) %>
{maybe_slice_string(@announcement.description, 300)}
</div>
<!-- Image -->
<%= if @announcement.image do %>
Expand Down
4 changes: 2 additions & 2 deletions lib/atomic_web/components/avatar.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ defmodule AtomicWeb.Components.Avatar do
<img src={@src} class={"atomic-avatar--#{assigns.type} h-full w-full"} />
<% else %>
<%= if @auto_generate_initials do %>
<%= extract_initials(@name) %>
{extract_initials(@name)}
<% else %>
<%= @name %>
{@name}
<% end %>
<% end %>
</span>
Expand Down
2 changes: 1 addition & 1 deletion lib/atomic_web/components/badge.ex
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ defmodule AtomicWeb.Components.Badge do
<%= if @icon && @icon_position == :left do %>
<.icon name={@icon} class={"#{generate_icon_classes(assigns)}"} />
<% end %>
<%= render_slot(@inner_block) || @label %>
{render_slot(@inner_block) || @label}
<%= if @icon && @icon_position == :right do %>
<.icon name={@icon} class={"#{generate_icon_classes(assigns)}"} />
<% end %>
Expand Down
14 changes: 7 additions & 7 deletions lib/atomic_web/components/button.ex
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,15 @@ defmodule AtomicWeb.Components.Button do
defp render_button(assigns) do
~H"""
<button class={@class} {@rest}>
<%= render_content(assigns) %>
{render_content(assigns)}
</button>
"""
end

defp link_button(assigns) do
~H"""
<.link class={@class} {@rest}>
<%= render_content(assigns) %>
{render_content(assigns)}
</.link>
"""
end
Expand All @@ -96,23 +96,23 @@ defmodule AtomicWeb.Components.Button do
<%= if (@icon || @spinner) && @icon_position == :left do %>
<div>
<%= if @icon do %>
<%= icon_content(assigns) %>
{icon_content(assigns)}
<% end %>
<%= if @spinner do %>
<%= spinner_content(assigns) %>
{spinner_content(assigns)}
<% end %>
</div>
<% end %>
<%= if Map.has_key?(assigns, :inner_block) do %>
<%= render_slot(@inner_block) %>
{render_slot(@inner_block)}
<% end %>
<%= if (@icon || @spinner) && @icon_position == :right do %>
<div>
<%= if @icon do %>
<%= icon_content(assigns) %>
{icon_content(assigns)}
<% end %>
<%= if @spinner do %>
<%= spinner_content(assigns) %>
{spinner_content(assigns)}
<% end %>
</div>
<% end %>
Expand Down
8 changes: 4 additions & 4 deletions lib/atomic_web/components/dropdown.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ defmodule AtomicWeb.Components.Dropdown do
~H"""
<div class="relative inline-block text-left" phx-click-away={JS.hide(to: "##{@id}", transition: {"ease-in duration-75", "transform opacity-100 scale-100", "transform opacity-0 scale-95"})}>
<div phx-click={JS.toggle(to: "##{@id}", in: {"ease-out duration-100", "transform opacity-0 scale-95", "transform opacity-100 scale-100"}, out: {"ease-in duration-75", "transform opacity-100 scale-100", "transform opacity-0 scale-95"}, display: "block")}>
<%= render_slot(@wrapper) %>
{render_slot(@wrapper)}
</div>
<div id={@id} class={"#{if @orientation == :down, do: "origin-top-right top-full mt-3", else: "origin-bottom-right bottom-full mb-3"} absolute right-0 z-10 hidden w-56 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5"}>
<div id={@id} class={"#{if @orientation == :down, do: "top-full mt-3 origin-top-right", else: "bottom-full mb-3 origin-bottom-right"} absolute right-0 z-10 hidden w-56 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5"}>
<div class="py-1" role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
<%= for item <- @items do %>
<%= if item[:patch] || item[:navigate] || item[:href] || item[:phx_click] do %>
Expand All @@ -48,14 +48,14 @@ defmodule AtomicWeb.Components.Dropdown do
<%= if item[:icon] do %>
<.icon name={item.icon} class="size-5 ml-2 inline-block" />
<% end %>
<%= item.name %>
{item.name}
</.link>
<% else %>
<div class={"#{item[:class]} flex items-center gap-x-2 px-4 py-2 text-sm text-zinc-700"}>
<%= if item[:icon] do %>
<.icon name={item.icon} class="size-5 ml-2 inline-block" />
<% end %>
<%= item.name %>
{item.name}
</div>
<% end %>
<% end %>
Expand Down
6 changes: 3 additions & 3 deletions lib/atomic_web/components/empty.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ defmodule AtomicWeb.Components.Empty do
~H"""
<div id={@id} class="text-center">
<.icon name="hero-plus-circle" class="size-12 mx-auto text-zinc-400" />
<h3 class="mt-2 text-sm font-semibold text-zinc-900">No <%= plural(@placeholder) %></h3>
<p class="mt-1 text-sm text-zinc-500">Get started by creating a new <%= @placeholder %>.</p>
<h3 class="mt-2 text-sm font-semibold text-zinc-900">No {plural(@placeholder)}</h3>
<p class="mt-1 text-sm text-zinc-500">Get started by creating a new {@placeholder}.</p>
<div class="mt-4">
<.link navigate={@url} class="bg-primary-500 inline-flex items-center rounded-md px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-primary-600 focus-visible:outline-primary-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2">
<svg class="size-5 mr-1.5 -ml-0.5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z" />
</svg>
New <%= @placeholder %>
New {@placeholder}
</.link>
</div>
</div>
Expand Down
Loading
Loading