mirror of
https://github.com/ClaperCo/Claper.git
synced 2026-07-09 20:09:55 +02:00
Squashed commit of the following:
commit7db48f592fAuthor: Alex Lion <dev@alexandrelion.com> Date: Fri Jun 5 10:16:10 2026 +0200 Update changelog commit3039b70edcAuthor: Peter Rundqvist <47314226+pete1854@users.noreply.github.com> Date: Fri Jun 5 10:11:07 2026 +0200 Add swedish translation (#228) * Initial translation * Initial error msg translation * Fixes suggested by Copilot. * Fixed spacing before ! and ? * Added missing provider translation. * Upddated translation for "Unpin" * Added missing Content-Type header. commitd18e5d7c32Author: fgallarday <fgallarday@users.noreply.github.com> Date: Fri Jun 5 03:07:25 2026 -0500 Add PageUp and PageDown slide navigation support (#227) commit24e18c2764Author: Alex Lion <dev@alexandrelion.com> Date: Fri Jun 5 10:06:54 2026 +0200 Bump version to 2.5.2 commitd5df9a73f8Author: Alex Lion <dev@alexandrelion.com> Date: Fri May 8 22:42:04 2026 +0200 Fix form submissions losing values when field names contain spaces or non-word characters commit57596952d4Author: Alex Lion <dev@alexandrelion.com> Date: Fri May 8 14:50:33 2026 +0200 Update package-lock.json: add asset name, upgrade dependencies, and remove unused packages commite246530c23Author: Alex Lion <dev@alexandrelion.com> Date: Fri May 8 14:34:53 2026 +0200 Bump version to 2.5.1 commit534d915782Author: Alex Lion <dev@alexandrelion.com> Date: Fri May 8 10:55:38 2026 +0200 Handle Ecto.NoResultsError in event retrieval and redirect with error message commitb762e21325Author: Alex Lion <dev@alexandrelion.com> Date: Fri May 8 10:47:01 2026 +0200 Fix crash on event manager pages when an event has multiple activity leaders commit153cadb42bAuthor: alxlion <dev@alexandrelion.com> Date: Wed Apr 29 13:42:05 2026 +0000 Fix presentation views without slide count commit3cfb3cab7fAuthor: alxlion <dev@alexandrelion.com> Date: Wed Apr 29 13:16:48 2026 +0000 Fix event code normalization validation
This commit is contained in:
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,4 +1,21 @@
|
||||
### v.2.5.0
|
||||
## v.2.5.2
|
||||
|
||||
### Fixes and improvements
|
||||
|
||||
- Add Swedish translation (#228)
|
||||
- Add PageUp and PageDown support for slide navigation (#227)
|
||||
|
||||
## v.2.5.1
|
||||
|
||||
### Security
|
||||
|
||||
- Update JS dependencies with high CVE
|
||||
|
||||
### Fixes and improvements
|
||||
|
||||
- Fix form submissions losing values when field names contain spaces or non-word characters
|
||||
|
||||
## v.2.5.0
|
||||
|
||||
### Features
|
||||
|
||||
@@ -24,8 +41,10 @@
|
||||
- Fix quizz real time average score update and id duplication
|
||||
- Fix crash when broadcasting events to leaders with unregistered emails
|
||||
- Fix OIDC compatibility with providers like Authelia and Microsoft Entra ID (#216) (#143) (#195)
|
||||
- Fix manager and presenter views while presentation conversion has no slide count yet
|
||||
- Fix crash on event manager pages when an event has multiple activity leaders
|
||||
|
||||
### v.2.4.0
|
||||
## v.2.4.0
|
||||
|
||||
### ⚠️ Breaking changes
|
||||
|
||||
@@ -57,7 +76,7 @@
|
||||
- Fix italian translation (#179)
|
||||
- Fix random poll choices (#184)
|
||||
|
||||
### v.2.3.2
|
||||
## v.2.3.2
|
||||
|
||||
### Fixes and improvements
|
||||
|
||||
@@ -69,7 +88,7 @@
|
||||
- Fix event code length validation (min: 5, max: 10)
|
||||
- Fix presentation upload progress when editing an event
|
||||
|
||||
### v.2.3.1
|
||||
## v.2.3.1
|
||||
|
||||
### Fixes and improvements
|
||||
|
||||
@@ -80,7 +99,7 @@
|
||||
- Add option to force login to submit quizzes
|
||||
- Fix url with question mark being flagged as a question
|
||||
|
||||
### v.2.3.0
|
||||
## v.2.3.0
|
||||
|
||||
### Features
|
||||
|
||||
|
||||
@@ -36,10 +36,12 @@ export class Manager {
|
||||
|
||||
switch (e.key) {
|
||||
case "ArrowLeft":
|
||||
case "PageUp":
|
||||
e.preventDefault();
|
||||
this.prevPage();
|
||||
break;
|
||||
case "ArrowRight":
|
||||
case "PageDown":
|
||||
e.preventDefault();
|
||||
this.nextPage();
|
||||
break;
|
||||
|
||||
@@ -116,12 +116,14 @@ export class Presenter {
|
||||
this.fullscreen();
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
case "PageUp":
|
||||
e.preventDefault();
|
||||
window.opener.dispatchEvent(
|
||||
new KeyboardEvent("keydown", { key: "ArrowLeft" })
|
||||
);
|
||||
break;
|
||||
case "ArrowRight":
|
||||
case "PageDown":
|
||||
e.preventDefault();
|
||||
window.opener.dispatchEvent(
|
||||
new KeyboardEvent("keydown", { key: "ArrowRight" })
|
||||
|
||||
182
assets/package-lock.json
generated
182
assets/package-lock.json
generated
@@ -773,6 +773,66 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
|
||||
"version": "1.4.3",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.0.2",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||
"version": "1.4.3",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.0.2",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "0.2.11",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "^1.4.3",
|
||||
"@emnapi/runtime": "^1.4.3",
|
||||
"@tybys/wasm-util": "^0.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
|
||||
"version": "0.9.0",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
|
||||
"version": "2.8.0",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "0BSD",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz",
|
||||
@@ -1238,9 +1298,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
|
||||
"integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
|
||||
"integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1250,22 +1310,6 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
|
||||
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mkdirp": "dist/cjs/src/bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.29.4",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||
@@ -1324,9 +1368,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
||||
"version": "8.5.14",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
|
||||
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -1403,17 +1447,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "7.4.3",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
|
||||
"integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
|
||||
"version": "7.5.15",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz",
|
||||
"integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"@isaacs/fs-minipass": "^4.0.0",
|
||||
"chownr": "^3.0.0",
|
||||
"minipass": "^7.1.2",
|
||||
"minizlib": "^3.0.1",
|
||||
"mkdirp": "^3.0.1",
|
||||
"minizlib": "^3.1.0",
|
||||
"yallist": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1787,6 +1830,62 @@
|
||||
"@napi-rs/wasm-runtime": "^0.2.11",
|
||||
"@tybys/wasm-util": "^0.9.0",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emnapi/core": {
|
||||
"version": "1.4.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@emnapi/wasi-threads": "1.0.2",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@emnapi/runtime": {
|
||||
"version": "1.4.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@emnapi/wasi-threads": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@napi-rs/wasm-runtime": {
|
||||
"version": "0.2.11",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@emnapi/core": "^1.4.3",
|
||||
"@emnapi/runtime": "^1.4.3",
|
||||
"@tybys/wasm-util": "^0.9.0"
|
||||
}
|
||||
},
|
||||
"@tybys/wasm-util": {
|
||||
"version": "0.9.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.8.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": {
|
||||
@@ -2042,20 +2141,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"minizlib": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
|
||||
"integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
|
||||
"integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minipass": "^7.1.2"
|
||||
}
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
|
||||
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
|
||||
"dev": true
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.29.4",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||
@@ -2110,9 +2203,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
||||
"version": "8.5.14",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
|
||||
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"nanoid": "^3.3.11",
|
||||
@@ -2162,16 +2255,15 @@
|
||||
"dev": true
|
||||
},
|
||||
"tar": {
|
||||
"version": "7.4.3",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
|
||||
"integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
|
||||
"version": "7.5.15",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz",
|
||||
"integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@isaacs/fs-minipass": "^4.0.0",
|
||||
"chownr": "^3.0.0",
|
||||
"minipass": "^7.1.2",
|
||||
"minizlib": "^3.0.1",
|
||||
"mkdirp": "^3.0.1",
|
||||
"minizlib": "^3.1.0",
|
||||
"yallist": "^5.0.0"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -293,6 +293,7 @@ defmodule Claper.Events do
|
||||
left_join: a in ActivityLeader,
|
||||
on: e.id == a.event_id,
|
||||
where: e.uuid == ^uuid and (u.id == ^user.id or a.email == ^user.email),
|
||||
distinct: true,
|
||||
select: e
|
||||
)
|
||||
|> Repo.one!()
|
||||
@@ -394,6 +395,9 @@ defmodule Claper.Events do
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_unique_event(%Ecto.Changeset{changes: %{code: nil}} = changeset),
|
||||
do: {:ok, changeset}
|
||||
|
||||
defp validate_unique_event(%Ecto.Changeset{changes: %{code: code} = _changes} = event) do
|
||||
case get_event_with_code(code) do
|
||||
%Event{} -> {:error, Ecto.Changeset.add_error(event, :code, "Already exists")}
|
||||
|
||||
@@ -80,19 +80,28 @@ defmodule Claper.Events.Event do
|
||||
|> cast(attrs, [:name, :code, :user_id, :started_at, :expired_at])
|
||||
|> cast_assoc(:presentation_file)
|
||||
|> cast_assoc(:leaders)
|
||||
|> downcase_code
|
||||
|> validate_required([:name, :code, :started_at, :user_id])
|
||||
|> validate_length(:code, min: 5, max: 10)
|
||||
|> validate_length(:name, min: 5, max: 50)
|
||||
|> downcase_code
|
||||
|> put_change(:uuid, Ecto.UUID.generate())
|
||||
end
|
||||
|
||||
def downcase_code(changeset) do
|
||||
update_change(
|
||||
changeset,
|
||||
:code,
|
||||
&(&1 |> String.downcase() |> String.split(~r"[^\w\d]", trim: true) |> List.first())
|
||||
)
|
||||
case fetch_change(changeset, :code) do
|
||||
{:ok, nil} ->
|
||||
changeset
|
||||
|
||||
{:ok, code} ->
|
||||
put_change(
|
||||
changeset,
|
||||
:code,
|
||||
code |> String.downcase() |> String.split(~r"[^\w\d]", trim: true) |> List.first()
|
||||
)
|
||||
|
||||
:error ->
|
||||
changeset
|
||||
end
|
||||
end
|
||||
|
||||
def update_changeset(event, attrs) do
|
||||
@@ -100,10 +109,10 @@ defmodule Claper.Events.Event do
|
||||
|> cast(attrs, [:name, :code, :started_at, :expired_at, :audience_peak, :user_id])
|
||||
|> cast_assoc(:presentation_file)
|
||||
|> cast_assoc(:leaders)
|
||||
|> downcase_code
|
||||
|> validate_required([:name, :code, :started_at, :user_id])
|
||||
|> validate_length(:code, min: 5, max: 10)
|
||||
|> validate_length(:name, min: 5, max: 50)
|
||||
|> downcase_code
|
||||
end
|
||||
|
||||
def restart_changeset(event) do
|
||||
|
||||
@@ -61,7 +61,7 @@ defmodule ClaperWeb.EventLive.FormComponent do
|
||||
form={f}
|
||||
labelClass="text-white"
|
||||
fieldClass="bg-gray-700 text-white"
|
||||
key={safe_field_atom(field.name)}
|
||||
key={field_key(field.name)}
|
||||
name={field.name}
|
||||
required={field.required}
|
||||
value={
|
||||
@@ -75,7 +75,7 @@ defmodule ClaperWeb.EventLive.FormComponent do
|
||||
form={f}
|
||||
labelClass="text-white"
|
||||
fieldClass="bg-gray-700 text-white"
|
||||
key={safe_field_atom(field.name)}
|
||||
key={field_key(field.name)}
|
||||
name={field.name}
|
||||
required={field.required}
|
||||
value={
|
||||
@@ -176,13 +176,12 @@ defmodule ClaperWeb.EventLive.FormComponent do
|
||||
end
|
||||
end
|
||||
|
||||
defp safe_field_atom(name) when is_binary(name) do
|
||||
String.to_existing_atom(name)
|
||||
rescue
|
||||
ArgumentError ->
|
||||
if Regex.match?(~r/^[\w]{1,100}$/, name),
|
||||
do: String.to_atom(name),
|
||||
else: :invalid_field
|
||||
defp field_key(name) when is_binary(name) do
|
||||
try do
|
||||
String.to_existing_atom(name)
|
||||
rescue
|
||||
ArgumentError -> String.to_atom(name)
|
||||
end
|
||||
end
|
||||
|
||||
def toggle_form(js \\ %JS{}) do
|
||||
|
||||
@@ -168,6 +168,11 @@ defmodule ClaperWeb.EventLive.Index do
|
||||
|> assign(:page_title, gettext("Edit"))
|
||||
|> assign(:event, event)
|
||||
end
|
||||
rescue
|
||||
Ecto.NoResultsError ->
|
||||
socket
|
||||
|> put_flash(:error, gettext("Event doesn't exist"))
|
||||
|> redirect(to: ~p"/events")
|
||||
end
|
||||
|
||||
defp apply_action(socket, :new, _params) do
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<div
|
||||
id="presenter"
|
||||
phx-hook="Presenter"
|
||||
data-max-page={@event.presentation_file.length}
|
||||
data-max-page={@event.presentation_file.length || 0}
|
||||
data-hash={@event.presentation_file.hash}
|
||||
data-current-page={@state.position}
|
||||
data-embed-active={if @current_embed, do: "true", else: "false"}
|
||||
@@ -90,7 +90,7 @@
|
||||
<!-- MESSAGES -->
|
||||
<div
|
||||
id="slider-wrapper"
|
||||
class={"w-full min-h-screen grid #{if (@state.chat_visible && @event.presentation_file.length > 0) || (@current_embed && @event.presentation_file.length == 0), do: "grid-cols-[1fr_10px_1fr]", else: "grid-cols-[1fr]"} items-center justify-center relative bg-black"}
|
||||
class={"w-full min-h-screen grid #{if (@state.chat_visible && (@event.presentation_file.length || 0) > 0) || (@current_embed && (@event.presentation_file.length || 0) == 0), do: "grid-cols-[1fr_10px_1fr]", else: "grid-cols-[1fr]"} items-center justify-center relative bg-black"}
|
||||
phx-hook="Split"
|
||||
data-type="column"
|
||||
data-gutter=".gutter-1"
|
||||
@@ -198,7 +198,7 @@
|
||||
<% end %>
|
||||
</div>
|
||||
<div
|
||||
class={"gutter-1 row-span-full cursor-col-resize col-2 text-center text-sm leading-3 text-white #{if (!@state.chat_visible && @event.presentation_file.length > 0) || (!@current_embed && @event.presentation_file.length == 0), do: "hidden"}"}
|
||||
class={"gutter-1 row-span-full cursor-col-resize col-2 text-center text-sm leading-3 text-white #{if (!@state.chat_visible && (@event.presentation_file.length || 0) > 0) || (!@current_embed && (@event.presentation_file.length || 0) == 0), do: "hidden"}"}
|
||||
style="writing-mode: vertical-rl"
|
||||
>
|
||||
•••
|
||||
|
||||
2
mix.exs
2
mix.exs
@@ -1,7 +1,7 @@
|
||||
defmodule Claper.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
@version "2.5.0"
|
||||
@version "2.5.2"
|
||||
|
||||
def project do
|
||||
[
|
||||
|
||||
2919
priv/gettext/sv/LC_MESSAGES/default.po
Normal file
2919
priv/gettext/sv/LC_MESSAGES/default.po
Normal file
File diff suppressed because it is too large
Load Diff
99
priv/gettext/sv/LC_MESSAGES/errors.po
Normal file
99
priv/gettext/sv/LC_MESSAGES/errors.po
Normal file
@@ -0,0 +1,99 @@
|
||||
## `msgid`s in this file come from POT (.pot) files.
|
||||
##
|
||||
## Do not add, change, or remove `msgid`s manually here as
|
||||
## they're tied to the ones in the corresponding POT file
|
||||
## (with the same domain).
|
||||
##
|
||||
## Use `mix gettext.extract --merge` or `mix gettext.merge`
|
||||
## to merge POT files into PO files.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Language: sv\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
## From Ecto.Changeset.cast/4
|
||||
msgid "can't be blank"
|
||||
msgstr "får inte vara tom"
|
||||
|
||||
## From Ecto.Changeset.unique_constraint/3
|
||||
msgid "has already been taken"
|
||||
msgstr "har redan tagits"
|
||||
|
||||
## From Ecto.Changeset.put_change/3
|
||||
msgid "is invalid"
|
||||
msgstr "är ogiltig"
|
||||
|
||||
## From Ecto.Changeset.validate_acceptance/3
|
||||
msgid "must be accepted"
|
||||
msgstr "måste accepteras"
|
||||
|
||||
## From Ecto.Changeset.validate_format/3
|
||||
msgid "has invalid format"
|
||||
msgstr "har ogiltigt format"
|
||||
|
||||
## From Ecto.Changeset.validate_subset/3
|
||||
msgid "has an invalid entry"
|
||||
msgstr "har en ogiltig post"
|
||||
|
||||
## From Ecto.Changeset.validate_exclusion/3
|
||||
msgid "is reserved"
|
||||
msgstr "är reserverad"
|
||||
|
||||
## From Ecto.Changeset.validate_confirmation/3
|
||||
msgid "does not match confirmation"
|
||||
msgstr "matchar inte bekräftelsen"
|
||||
|
||||
## From Ecto.Changeset.no_assoc_constraint/3
|
||||
msgid "is still associated with this entry"
|
||||
msgstr "associeras fortfarande med den här posten"
|
||||
|
||||
msgid "are still associated with this entry"
|
||||
msgstr "associeras fortfarande med denna post"
|
||||
|
||||
msgid "should be %{count} character(s)"
|
||||
msgid_plural "should be %{count} character(s)"
|
||||
msgstr[0] "måste innehålla %{count} tecken"
|
||||
msgstr[1] "måste innehålla %{count} tecken"
|
||||
|
||||
## From Ecto.Changeset.validate_length/3
|
||||
msgid "should have %{count} item(s)"
|
||||
msgid_plural "should have %{count} item(s)"
|
||||
msgstr[0] "måste innehålla %{count} objekt"
|
||||
msgstr[1] "måste innehålla %{count} objekt"
|
||||
|
||||
msgid "should be at least %{count} character(s)"
|
||||
msgid_plural "should be at least %{count} character(s)"
|
||||
msgstr[0] "måste innehålla minst %{count} tecken"
|
||||
msgstr[1] "måste innehålla minst %{count} tecken"
|
||||
|
||||
msgid "should have at least %{count} item(s)"
|
||||
msgid_plural "should have at least %{count} item(s)"
|
||||
msgstr[0] "måste innehålla minst %{count} objekt"
|
||||
msgstr[1] "måste innehålla minst %{count} objekt"
|
||||
|
||||
msgid "should be at most %{count} character(s)"
|
||||
msgid_plural "should be at most %{count} character(s)"
|
||||
msgstr[0] "får innehålla högst %{count} tecken"
|
||||
msgstr[1] "får innehålla högst %{count} tecken"
|
||||
|
||||
msgid "should have at most %{count} item(s)"
|
||||
msgid_plural "should have at most %{count} item(s)"
|
||||
msgstr[0] "får innehålla högst %{count} objekt"
|
||||
msgstr[1] "får innehålla högst %{count} objekt"
|
||||
|
||||
## From Ecto.Changeset.validate_number/3
|
||||
msgid "must be less than %{number}"
|
||||
msgstr "måste vara mindre än %{number}"
|
||||
|
||||
msgid "must be greater than %{number}"
|
||||
msgstr "måste vara större än %{number}"
|
||||
|
||||
msgid "must be less than or equal to %{number}"
|
||||
msgstr "måste vara mindre än eller lika med %{number}"
|
||||
|
||||
msgid "must be greater than or equal to %{number}"
|
||||
msgstr "måste vara större än eller lika med %{number}"
|
||||
|
||||
msgid "must be equal to %{number}"
|
||||
msgstr "måste vara lika med %{number}"
|
||||
@@ -232,6 +232,16 @@ defmodule Claper.EventsTest do
|
||||
assert Events.get_managed_event!(context.alice, event.uuid) == event
|
||||
end
|
||||
|
||||
test "get_managed_event!/3 does not duplicate when an event has multiple leaders",
|
||||
context do
|
||||
event = Enum.at(context.carol_active_events, 1)
|
||||
activity_leader_fixture(%{event: event, email: "extra-leader-1@example.com"})
|
||||
activity_leader_fixture(%{event: event, email: "extra-leader-2@example.com"})
|
||||
|
||||
assert Events.get_managed_event!(context.carol, event.uuid) == event
|
||||
assert Events.get_managed_event!(context.alice, event.uuid) == event
|
||||
end
|
||||
|
||||
test "get_user_event!/3 gets event by owner, raises if not", context do
|
||||
event = Enum.at(context.alice_active_events, 0)
|
||||
assert Events.get_user_event!(context.alice.id, event.uuid) == event
|
||||
@@ -452,6 +462,16 @@ defmodule Claper.EventsTest do
|
||||
assert event == Events.get_event!(event.uuid)
|
||||
end
|
||||
|
||||
test "update_event/2 with code that normalizes to nil returns error changeset" do
|
||||
event = event_fixture()
|
||||
|
||||
assert {:error, %Ecto.Changeset{} = changeset} =
|
||||
Events.update_event(event, %{code: "------"})
|
||||
|
||||
assert %{code: ["can't be blank"]} = errors_on(changeset)
|
||||
assert event == Events.get_event!(event.uuid)
|
||||
end
|
||||
|
||||
test "change_event/1 returns a event changeset" do
|
||||
event = event_fixture()
|
||||
assert %Ecto.Changeset{} = Events.change_event(event)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
defmodule ClaperWeb.StatControllerTest do
|
||||
use ClaperWeb.ConnCase
|
||||
use ClaperWeb.ConnCase, async: true
|
||||
|
||||
import Claper.AccountsFixtures
|
||||
import Claper.PresentationsFixtures
|
||||
import Claper.{AccountsFixtures, FormsFixtures, PresentationsFixtures}
|
||||
|
||||
describe "export_transcriptions/2" do
|
||||
test "exports timestamp, language, and text as CSV", %{conn: conn} do
|
||||
@@ -38,4 +37,58 @@ defmodule ClaperWeb.StatControllerTest do
|
||||
assert response(conn, 403) == "Forbidden"
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /export/forms/:form_id" do
|
||||
setup %{conn: conn} do
|
||||
owner = confirmed_user_fixture()
|
||||
presentation_file = presentation_file_fixture(%{user: owner}, [:event])
|
||||
|
||||
form =
|
||||
form_fixture(%{
|
||||
presentation_file_id: presentation_file.id,
|
||||
title: "My Form",
|
||||
fields: [
|
||||
%{name: "First Name", type: "text", required: false},
|
||||
%{name: "Email Address", type: "email", required: false}
|
||||
]
|
||||
})
|
||||
|
||||
%{conn: log_in_user(conn, owner), form: form, event: presentation_file.event}
|
||||
end
|
||||
|
||||
test "exports CSV with field names containing spaces as headers and values", %{
|
||||
conn: conn,
|
||||
form: form,
|
||||
event: event
|
||||
} do
|
||||
{:ok, _submit} =
|
||||
Claper.Forms.create_or_update_form_submit(event.uuid, %{
|
||||
"attendee_identifier" => "attendee-1",
|
||||
"form_id" => form.id,
|
||||
"response" => %{"First Name" => "Ada", "Email Address" => "ada@example.com"}
|
||||
})
|
||||
|
||||
conn = post(conn, ~p"/export/forms/#{form.id}")
|
||||
|
||||
assert response_content_type(conn, :csv)
|
||||
body = response(conn, 200)
|
||||
|
||||
[header_line, data_line | _] = String.split(body, "\r\n", trim: true)
|
||||
|
||||
assert header_line =~ "First Name"
|
||||
assert header_line =~ "Email Address"
|
||||
|
||||
assert data_line =~ "Ada"
|
||||
assert data_line =~ "ada@example.com"
|
||||
end
|
||||
|
||||
test "returns 403 for users who don't own the event", %{form: form} do
|
||||
stranger = confirmed_user_fixture()
|
||||
conn = build_conn() |> log_in_user(stranger)
|
||||
|
||||
conn = post(conn, ~p"/export/forms/#{form.id}")
|
||||
|
||||
assert response(conn, 403) == "Forbidden"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
72
test/claper_web/live/event_live/form_component_test.exs
Normal file
72
test/claper_web/live/event_live/form_component_test.exs
Normal file
@@ -0,0 +1,72 @@
|
||||
defmodule ClaperWeb.EventLive.FormComponentTest do
|
||||
use ClaperWeb.ConnCase, async: true
|
||||
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
import Claper.{AccountsFixtures, FormsFixtures, PresentationsFixtures}
|
||||
|
||||
alias ClaperWeb.EventLive.FormComponent
|
||||
|
||||
defp setup_form(field_attrs) do
|
||||
presentation_file = presentation_file_fixture(%{}, [:event])
|
||||
|
||||
form =
|
||||
form_fixture(%{
|
||||
presentation_file_id: presentation_file.id,
|
||||
fields: field_attrs
|
||||
})
|
||||
|
||||
%{event: presentation_file.event, form: form}
|
||||
end
|
||||
|
||||
describe "rendering fields with non-alphanumeric names" do
|
||||
test "renders an input whose name uses the original field name (with spaces)" do
|
||||
%{event: event, form: form} =
|
||||
setup_form([
|
||||
%{name: "First Name", type: "text", required: false},
|
||||
%{name: "Email Address", type: "email", required: false}
|
||||
])
|
||||
|
||||
html =
|
||||
render_component(FormComponent,
|
||||
id: "form-component",
|
||||
form: form,
|
||||
current_user: user_fixture(),
|
||||
attendee_identifier: nil,
|
||||
event: event,
|
||||
current_form_submit: nil
|
||||
)
|
||||
|
||||
# The HTML name attribute must contain the actual field name —
|
||||
# regression: a previous version mapped any non-\w name to :invalid_field,
|
||||
# so all space-containing fields collided on `form_submit[invalid_field]`
|
||||
# and submissions were saved under the wrong key.
|
||||
assert html =~ ~s(name="form_submit[First Name]")
|
||||
assert html =~ ~s(name="form_submit[Email Address]")
|
||||
refute html =~ "invalid_field"
|
||||
end
|
||||
|
||||
test "preserves submitted values when re-rendering for fields with spaces" do
|
||||
%{event: event, form: form} =
|
||||
setup_form([
|
||||
%{name: "First Name", type: "text", required: false}
|
||||
])
|
||||
|
||||
form_submit = %Claper.Forms.FormSubmit{
|
||||
response: %{"First Name" => "Ada"}
|
||||
}
|
||||
|
||||
html =
|
||||
render_component(FormComponent,
|
||||
id: "form-component",
|
||||
form: form,
|
||||
current_user: user_fixture(),
|
||||
attendee_identifier: nil,
|
||||
event: event,
|
||||
current_form_submit: form_submit
|
||||
)
|
||||
|
||||
assert html =~ ~s(value="Ada")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -38,7 +38,7 @@ defmodule Claper.EventsFixtures do
|
||||
attrs
|
||||
|> Enum.into(%{
|
||||
name: "some name",
|
||||
code: :crypto.strong_rand_bytes(4) |> Base.url_encode64() |> binary_part(0, 8),
|
||||
code: Base.encode16(:crypto.strong_rand_bytes(4), case: :lower),
|
||||
uuid: Ecto.UUID.generate(),
|
||||
user_id: assoc.user.id,
|
||||
started_at: NaiveDateTime.utc_now(),
|
||||
|
||||
Reference in New Issue
Block a user