Compare commits

..

1 commit
main ... v0.1.0

Author SHA1 Message Date
97546fb48f
ci: add release workflow
All checks were successful
Checks / check (push) Successful in 1m34s
Build and Push Docker Image / Build and Push Image (push) Successful in 37s
2024-11-30 20:26:10 +02:00
33 changed files with 200 additions and 1412 deletions

View file

@ -1,67 +0,0 @@
name: Manual Release and Docker Image Build
on:
workflow_dispatch:
inputs:
version:
description: "Version for the release (e.g., v1.0.0)"
required: true
default: "v1.0.0"
type: string
jobs:
release:
name: Create Release and Build Docker Image
runs-on: ubuntu-latest-root
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Login to Docker Registry
uses: docker/login-action@v3
with:
registry: git.vavakado.xyz
username: vavakado
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Create Git Tag
run: |
git tag ${{ github.event.inputs.version }}
git push origin ${{ github.event.inputs.version }}
# - name: Create Release
# run: |
# curl -X POST -H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \
# -H "Content-Type: application/json" \
# -d '{
# "tag_name": "${{ github.event.inputs.version }}",
# "target_commitish": "main", # Change if your default branch is different
# "name": "${{ github.event.inputs.version }}",
# "body": "Release for version ${{ github.event.inputs.version }}",
# "draft": false,
# "prerelease": false
# }' \
# https://git.vavakado.xyz/api/v1/repos/${{ gitea.repository_owner }}/${{ gitea.repository }}/releases
# - name: Create Release
# uses: actions/forgejo-release@v2
# with:
# direction: upload
# tag: ${{ github.event.inputs.version }}
# title: ${{ github.event.inputs.version }}
# token: ${{ secrets.RELEASE_TOKEN }}
# release-notes-assistant: true
# release-dir: ./
- name: Build Docker Image
run: |
docker build -t git.vavakado.xyz/${{ gitea.repository_owner }}/exmr:${{ github.event.inputs.version }} .
docker tag git.vavakado.xyz/vavakado/exmr:${{ github.event.inputs.version }} git.vavakado.xyz/vavakado/exmr:latest
docker tag git.vavakado.xyz/vavakado/exmr:${{ github.event.inputs.version }} git.vavakado.xyz/vavakado/exmr:stable
- name: Push Docker Image
run: |
docker push git.vavakado.xyz/${{ gitea.repository_owner }}/exmr:${{ github.event.inputs.version }}
docker push git.vavakado.xyz/vavakado/exmr:latest
docker push git.vavakado.xyz/vavakado/exmr:stable

View file

@ -19,7 +19,7 @@ jobs:
- name: Setup ssl - name: Setup ssl
run: wget http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2.23_amd64.deb && sudo dpkg -i libssl1.1_1.1.1f-1ubuntu2.23_amd64.deb run: wget http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2.23_amd64.deb && sudo dpkg -i libssl1.1_1.1.1f-1ubuntu2.23_amd64.deb
- uses: https://github.com/erlef/setup-beam@v1 - uses: erlef/setup-beam@v1
env: env:
ImageOS: ubuntu24 ImageOS: ubuntu24
with: with:

View file

@ -3,12 +3,12 @@ name: Build and Push Docker Image
on: on:
push: push:
branches: branches:
- main - main # Change this to your default branch if different
jobs: jobs:
build: build:
name: Build and Push Image name: Build and Push Image
runs-on: ubuntu-latest-root runs-on: ubuntu-latest-root # This specifies the environment for the job
steps: steps:
- name: Checkout Repository - name: Checkout Repository
@ -17,9 +17,9 @@ jobs:
- name: Login to Docker Registry - name: Login to Docker Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: git.vavakado.xyz registry: git.vavakado.xyz # Replace with your Docker registry URL
username: vavakado username: ${{ gitea.actor }} # The actor who triggered the action
password: ${{ secrets.REGISTRY_TOKEN }} password: ${{ secrets.REGISTRY_TOKEN }} # Your registry token secret
- name: Build Docker Image - name: Build Docker Image
run: | run: |

View file

@ -0,0 +1,52 @@
name: Manual Release and Docker Image Build
on:
workflow_dispatch:
inputs:
version:
description: "Version for the release (e.g., v1.0.0)"
required: true
default: "v1.0.0"
jobs:
release:
name: Create Release and Build Docker Image
runs-on: ubuntu-latest-root
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Login to Docker Registry
uses: docker/login-action@v3
with:
registry: git.vavakado.xyz # Replace with your Docker registry URL
username: ${{ gitea.actor }} # The actor who triggered the action
password: ${{ secrets.REGISTRY_TOKEN }} # Your registry token secret
- name: Create Git Tag
run: |
git tag ${{ github.event.inputs.version }}
git push origin ${{ github.event.inputs.version }}
- name: Create Release
run: |
curl -X POST -H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{
"tag_name": "${{ github.event.inputs.version }}",
"target_commitish": "main", # Change if your default branch is different
"name": "${{ github.event.inputs.version }}",
"body": "Release for version ${{ github.event.inputs.version }}",
"draft": false,
"prerelease": false
}' \
https://git.vavakado.xyz/api/v1/repos/${{ gitea.repository_owner }}/${{ gitea.repository }}/releases
- name: Build Docker Image
run: |
docker build -t git.vavakado.xyz/${{ gitea.repository_owner }}/your-image-name:${{ github.event.inputs.version }} .
- name: Push Docker Image
run: |
docker push git.vavakado.xyz/${{ gitea.repository_owner }}/your-image-name:${{ github.event.inputs.version }}

View file

@ -18,7 +18,7 @@ ARG DEBIAN_VERSION=bullseye-20241111-slim
ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}" ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}" ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"
FROM ${BUILDER_IMAGE} AS builder FROM ${BUILDER_IMAGE} as builder
# install build dependencies # install build dependencies
RUN apt-get update -y && apt-get install -y build-essential git \ RUN apt-get update -y && apt-get install -y build-essential git \

View file

@ -1,8 +0,0 @@
Copyright 2024 Vladimir Rubin
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -9,8 +9,12 @@ To start your Phoenix server:
Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
## TODO Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html).
- [ ] Add tests ## Learn more
- [ ] Add a proper readme
- [ ] Add a calendar view - Official website: https://www.phoenixframework.org/
- Guides: https://hexdocs.pm/phoenix/overview.html
- Docs: https://hexdocs.pm/phoenix
- Forum: https://elixirforum.com/c/phoenix-forum
- Source: https://github.com/phoenixframework/phoenix

View file

@ -16,35 +16,29 @@
// //
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons. // Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
import "phoenix_html"; import "phoenix_html"
// Establish Phoenix Socket and LiveView configuration. // Establish Phoenix Socket and LiveView configuration.
import { Socket } from "phoenix"; import {Socket} from "phoenix"
import { LiveSocket } from "phoenix_live_view"; import {LiveSocket} from "phoenix_live_view"
import topbar from "../vendor/topbar"; import topbar from "../vendor/topbar"
import darkModeHook from "../vendor/dark_mode"; let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let Hooks = {};
Hooks.DarkThemeToggle = darkModeHook;
let csrfToken = document
.querySelector("meta[name='csrf-token']")
.getAttribute("content");
let liveSocket = new LiveSocket("/live", Socket, { let liveSocket = new LiveSocket("/live", Socket, {
longPollFallbackMs: 2500, longPollFallbackMs: 2500,
params: { _csrf_token: csrfToken }, params: {_csrf_token: csrfToken}
hooks: Hooks, })
});
// Show progress bar on live navigation and form submits // Show progress bar on live navigation and form submits
topbar.config({ barColors: { 0: "#29d" }, shadowColor: "rgba(0, 0, 0, .3)" }); topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
window.addEventListener("phx:page-loading-start", (_info) => topbar.show(300)); window.addEventListener("phx:page-loading-start", _info => topbar.show(300))
window.addEventListener("phx:page-loading-stop", (_info) => topbar.hide()); window.addEventListener("phx:page-loading-stop", _info => topbar.hide())
// connect if there are any LiveViews on the page // connect if there are any LiveViews on the page
liveSocket.connect(); liveSocket.connect()
// expose liveSocket on window for web console debug logs and latency simulation: // expose liveSocket on window for web console debug logs and latency simulation:
// >> liveSocket.enableDebug() // >> liveSocket.enableDebug()
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
// >> liveSocket.disableLatencySim() // >> liveSocket.disableLatencySim()
window.liveSocket = liveSocket; window.liveSocket = liveSocket

View file

@ -7,7 +7,6 @@ const path = require("path");
module.exports = { module.exports = {
content: ["./js/**/*.js", "../lib/exmr_web.ex", "../lib/exmr_web/**/*.*ex"], content: ["./js/**/*.js", "../lib/exmr_web.ex", "../lib/exmr_web/**/*.*ex"],
darkMode: "class",
theme: { theme: {
extend: { extend: {
colors: { colors: {

View file

@ -1,46 +0,0 @@
const localStorageKey = "theme";
const isDark = () => {
if (localStorage.getItem(localStorageKey) === "dark") return true;
if (localStorage.getItem(localStorageKey) === "light") return false;
return window.matchMedia("(prefers-color-scheme: dark)").matches;
};
const setupThemeToggle = () => {
toggleVisibility = (dark) => {
const themeToggleDarkIcon = document.getElementById(
"theme-toggle-dark-icon",
);
const themeToggleLightIcon = document.getElementById(
"theme-toggle-light-icon",
);
if (themeToggleDarkIcon == null || themeToggleLightIcon == null) return;
const show = dark ? themeToggleDarkIcon : themeToggleLightIcon;
const hide = dark ? themeToggleLightIcon : themeToggleDarkIcon;
show.classList.remove("hidden", "text-transparent");
hide.classList.add("hidden", "text-transparent");
if (dark) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
try {
localStorage.setItem(localStorageKey, dark ? "dark" : "light");
} catch (_err) { }
};
toggleVisibility(isDark());
document
.getElementById("theme-toggle")
.addEventListener("click", function() {
toggleVisibility(!isDark());
});
};
const darkModeHook = {
mounted() {
setupThemeToggle();
},
updated() { },
};
export default darkModeHook;

View file

@ -48,13 +48,9 @@ defmodule ExmrWeb.CoreComponents do
phx-mounted={@show && show_modal(@id)} phx-mounted={@show && show_modal(@id)}
phx-remove={hide_modal(@id)} phx-remove={hide_modal(@id)}
data-cancel={JS.exec(@on_cancel, "phx-remove")} data-cancel={JS.exec(@on_cancel, "phx-remove")}
class="relative z-50 hidden bg-zinc-50/90 dark:bg-zinc-950" class="relative z-50 hidden"
> >
<div <div id={"#{@id}-bg"} class="bg-zinc-50/90 fixed inset-0 transition-opacity" aria-hidden="true" />
id={"#{@id}-bg"}
class="bg-zinc-50/90 dark:bg-zinc-800/90 fixed inset-0 transition-opacity"
aria-hidden="true"
/>
<div <div
class="fixed inset-0 overflow-y-auto" class="fixed inset-0 overflow-y-auto"
aria-labelledby={"#{@id}-title"} aria-labelledby={"#{@id}-title"}
@ -70,7 +66,7 @@ defmodule ExmrWeb.CoreComponents do
phx-window-keydown={JS.exec("data-cancel", to: "##{@id}")} phx-window-keydown={JS.exec("data-cancel", to: "##{@id}")}
phx-key="escape" phx-key="escape"
phx-click-away={JS.exec("data-cancel", to: "##{@id}")} phx-click-away={JS.exec("data-cancel", to: "##{@id}")}
class="shadow-zinc-700/10 ring-zinc-700/10 relative hidden rounded-2xl bg-white dark:bg-zinc-950 p-14 shadow-lg ring-1 transition" class="shadow-zinc-700/10 ring-zinc-700/10 relative hidden rounded-2xl bg-white p-14 shadow-lg ring-1 transition"
> >
<div class="absolute top-6 right-5"> <div class="absolute top-6 right-5">
<button <button
@ -83,7 +79,7 @@ defmodule ExmrWeb.CoreComponents do
</button> </button>
</div> </div>
<div id={"#{@id}-content"}> <div id={"#{@id}-content"}>
{render_slot(@inner_block)} <%= render_slot(@inner_block) %>
</div> </div>
</.focus_wrap> </.focus_wrap>
</div> </div>
@ -128,9 +124,9 @@ defmodule ExmrWeb.CoreComponents do
<p :if={@title} class="flex items-center gap-1.5 text-sm font-semibold leading-6"> <p :if={@title} class="flex items-center gap-1.5 text-sm font-semibold leading-6">
<.icon :if={@kind == :info} name="hero-information-circle-mini" class="h-4 w-4" /> <.icon :if={@kind == :info} name="hero-information-circle-mini" class="h-4 w-4" />
<.icon :if={@kind == :error} name="hero-exclamation-circle-mini" class="h-4 w-4" /> <.icon :if={@kind == :error} name="hero-exclamation-circle-mini" class="h-4 w-4" />
{@title} <%= @title %>
</p> </p>
<p class="mt-2 text-sm leading-5">{msg}</p> <p class="mt-2 text-sm leading-5"><%= msg %></p>
<button type="button" class="group absolute top-1 right-1 p-2" aria-label={gettext("close")}> <button type="button" class="group absolute top-1 right-1 p-2" aria-label={gettext("close")}>
<.icon name="hero-x-mark-solid" class="h-5 w-5 opacity-40 group-hover:opacity-70" /> <.icon name="hero-x-mark-solid" class="h-5 w-5 opacity-40 group-hover:opacity-70" />
</button> </button>
@ -161,7 +157,7 @@ defmodule ExmrWeb.CoreComponents do
phx-connected={hide("#client-error")} phx-connected={hide("#client-error")}
hidden hidden
> >
{gettext("Attempting to reconnect")} <%= gettext("Attempting to reconnect") %>
<.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" /> <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
</.flash> </.flash>
@ -173,7 +169,7 @@ defmodule ExmrWeb.CoreComponents do
phx-connected={hide("#server-error")} phx-connected={hide("#server-error")}
hidden hidden
> >
{gettext("Hang in there while we get back on track")} <%= gettext("Hang in there while we get back on track") %>
<.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" /> <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
</.flash> </.flash>
</div> </div>
@ -206,13 +202,10 @@ defmodule ExmrWeb.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="mt-10 space-y-8 bg-white dark:bg-zinc-950"> <div class="mt-10 space-y-8 bg-white">
{render_slot(@inner_block, f)} <%= render_slot(@inner_block, f) %>
<div <div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6">
:for={action <- @actions} <%= render_slot(action, f) %>
class="mt-2 flex items-center justify-between gap-6 dark:text-zinc-100"
>
{render_slot(action, f)}
</div> </div>
</div> </div>
</.form> </.form>
@ -239,12 +232,12 @@ defmodule ExmrWeb.CoreComponents do
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 hover:bg-zinc-700 py-2 px-3",
"text-sm font-semibold leading-6", "text-sm font-semibold leading-6 text-white active:text-white/80",
@class @class
]} ]}
{@rest} {@rest}
> >
{render_slot(@inner_block)} <%= render_slot(@inner_block) %>
</button> </button>
""" """
end end
@ -317,7 +310,7 @@ defmodule ExmrWeb.CoreComponents do
~H""" ~H"""
<div> <div>
<label class="flex items-center gap-4 text-sm leading-6 text-zinc-600 dark:text-zinc-200"> <label class="flex items-center gap-4 text-sm leading-6 text-zinc-600">
<input type="hidden" name={@name} value="false" disabled={@rest[:disabled]} /> <input type="hidden" name={@name} value="false" disabled={@rest[:disabled]} />
<input <input
type="checkbox" type="checkbox"
@ -328,9 +321,9 @@ defmodule ExmrWeb.CoreComponents do
class="rounded border-zinc-300 text-zinc-900 focus:ring-0" class="rounded border-zinc-300 text-zinc-900 focus:ring-0"
{@rest} {@rest}
/> />
{@label} <%= @label %>
</label> </label>
<.error :for={msg <- @errors}>{msg}</.error> <.error :for={msg <- @errors}><%= msg %></.error>
</div> </div>
""" """
end end
@ -338,7 +331,7 @@ defmodule ExmrWeb.CoreComponents do
def input(%{type: "select"} = assigns) do def input(%{type: "select"} = assigns) do
~H""" ~H"""
<div> <div>
<.label for={@id}>{@label}</.label> <.label for={@id}><%= @label %></.label>
<select <select
id={@id} id={@id}
name={@name} name={@name}
@ -346,10 +339,10 @@ defmodule ExmrWeb.CoreComponents do
multiple={@multiple} multiple={@multiple}
{@rest} {@rest}
> >
<option :if={@prompt} value="">{@prompt}</option> <option :if={@prompt} value=""><%= @prompt %></option>
{Phoenix.HTML.Form.options_for_select(@options, @value)} <%= Phoenix.HTML.Form.options_for_select(@options, @value) %>
</select> </select>
<.error :for={msg <- @errors}>{msg}</.error> <.error :for={msg <- @errors}><%= msg %></.error>
</div> </div>
""" """
end end
@ -357,7 +350,7 @@ defmodule ExmrWeb.CoreComponents do
def input(%{type: "textarea"} = assigns) do def input(%{type: "textarea"} = assigns) do
~H""" ~H"""
<div> <div>
<.label for={@id}>{@label}</.label> <.label for={@id}><%= @label %></.label>
<textarea <textarea
id={@id} id={@id}
name={@name} name={@name}
@ -368,7 +361,7 @@ defmodule ExmrWeb.CoreComponents do
]} ]}
{@rest} {@rest}
><%= Phoenix.HTML.Form.normalize_value("textarea", @value) %></textarea> ><%= Phoenix.HTML.Form.normalize_value("textarea", @value) %></textarea>
<.error :for={msg <- @errors}>{msg}</.error> <.error :for={msg <- @errors}><%= msg %></.error>
</div> </div>
""" """
end end
@ -377,7 +370,7 @@ defmodule ExmrWeb.CoreComponents do
def input(assigns) do def input(assigns) do
~H""" ~H"""
<div> <div>
<.label for={@id}>{@label}</.label> <.label for={@id}><%= @label %></.label>
<input <input
type={@type} type={@type}
name={@name} name={@name}
@ -390,7 +383,7 @@ defmodule ExmrWeb.CoreComponents do
]} ]}
{@rest} {@rest}
/> />
<.error :for={msg <- @errors}>{msg}</.error> <.error :for={msg <- @errors}><%= msg %></.error>
</div> </div>
""" """
end end
@ -403,8 +396,8 @@ defmodule ExmrWeb.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 dark:text-zinc-200"> <label for={@for} class="block text-sm font-semibold leading-6 text-zinc-800">
{render_slot(@inner_block)} <%= render_slot(@inner_block) %>
</label> </label>
""" """
end end
@ -418,7 +411,7 @@ defmodule ExmrWeb.CoreComponents do
~H""" ~H"""
<p class="mt-3 flex gap-3 text-sm leading-6 text-rose-600"> <p class="mt-3 flex gap-3 text-sm leading-6 text-rose-600">
<.icon name="hero-exclamation-circle-mini" class="mt-0.5 h-5 w-5 flex-none" /> <.icon name="hero-exclamation-circle-mini" class="mt-0.5 h-5 w-5 flex-none" />
{render_slot(@inner_block)} <%= render_slot(@inner_block) %>
</p> </p>
""" """
end end
@ -436,14 +429,14 @@ defmodule ExmrWeb.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-none text-zinc-800 dark:text-zinc-200"> <h1 class="text-lg font-semibold leading-8 text-zinc-800">
{render_slot(@inner_block)} <%= render_slot(@inner_block) %>
</h1> </h1>
<p :if={@subtitle != []} class="mt-2 text-sm leading-6 text-zinc-600 dark:text-zinc-100"> <p :if={@subtitle != []} class="mt-2 text-sm leading-6 text-zinc-600">
{render_slot(@subtitle)} <%= render_slot(@subtitle) %>
</p> </p>
</div> </div>
<div class="flex-none">{render_slot(@actions)}</div> <div class="flex-none"><%= render_slot(@actions) %></div>
</header> </header>
""" """
end end
@ -484,9 +477,9 @@ defmodule ExmrWeb.CoreComponents do
<table class="w-[40rem] mt-11 sm:w-full"> <table class="w-[40rem] mt-11 sm:w-full">
<thead class="text-sm text-left leading-6 text-zinc-500"> <thead class="text-sm text-left leading-6 text-zinc-500">
<tr> <tr>
<th :for={col <- @col} class="p-0 pb-4 pr-6 font-normal">{col[:label]}</th> <th :for={col <- @col} class="p-0 pb-4 pr-6 font-normal"><%= col[:label] %></th>
<th :if={@action != []} class="relative p-0 pb-4"> <th :if={@action != []} class="relative p-0 pb-4">
<span class="sr-only">{gettext("Actions")}</span> <span class="sr-only"><%= gettext("Actions") %></span>
</th> </th>
</tr> </tr>
</thead> </thead>
@ -504,7 +497,7 @@ defmodule ExmrWeb.CoreComponents do
<div class="block py-4 pr-6"> <div class="block py-4 pr-6">
<span class="absolute -inset-y-px right-0 -left-4 group-hover:bg-zinc-50 sm:rounded-l-xl" /> <span class="absolute -inset-y-px right-0 -left-4 group-hover:bg-zinc-50 sm:rounded-l-xl" />
<span class={["relative", i == 0 && "font-semibold text-zinc-900"]}> <span class={["relative", i == 0 && "font-semibold text-zinc-900"]}>
{render_slot(col, @row_item.(row))} <%= render_slot(col, @row_item.(row)) %>
</span> </span>
</div> </div>
</td> </td>
@ -515,7 +508,7 @@ defmodule ExmrWeb.CoreComponents do
: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"
> >
{render_slot(action, @row_item.(row))} <%= render_slot(action, @row_item.(row)) %>
</span> </span>
</div> </div>
</td> </td>
@ -545,8 +538,8 @@ defmodule ExmrWeb.CoreComponents do
<div class="mt-14"> <div class="mt-14">
<dl class="-my-4 divide-y divide-zinc-100"> <dl class="-my-4 divide-y divide-zinc-100">
<div :for={item <- @item} class="flex gap-4 py-4 text-sm leading-6 sm:gap-8"> <div :for={item <- @item} class="flex gap-4 py-4 text-sm leading-6 sm:gap-8">
<dt class="w-1/4 flex-none text-zinc-500">{item.title}</dt> <dt class="w-1/4 flex-none text-zinc-500"><%= item.title %></dt>
<dd class="text-zinc-700">{render_slot(item)}</dd> <dd class="text-zinc-700"><%= render_slot(item) %></dd>
</div> </div>
</dl> </dl>
</div> </div>
@ -571,7 +564,7 @@ defmodule ExmrWeb.CoreComponents do
class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700" class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
> >
<.icon name="hero-arrow-left-solid" class="h-3 w-3" /> <.icon name="hero-arrow-left-solid" class="h-3 w-3" />
{render_slot(@inner_block)} <%= render_slot(@inner_block) %>
</.link> </.link>
</div> </div>
""" """

View file

@ -1,57 +0,0 @@
defmodule DarkMode do
@moduledoc """
A component to toggle dark mode.
"""
use Phoenix.Component
def button(assigns) do
~H"""
<button
id="theme-toggle"
type="button"
phx-update="ignore"
phx-hook="DarkThemeToggle"
class="text-zinc-500 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-700 rounded-lg text-sm p-2"
>
<svg
id="theme-toggle-dark-icon"
class="w-5 h-5 text-transparent hidden"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path>
</svg>
<svg
id="theme-toggle-light-icon"
class="w-5 h-5 text-transparent"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
fill-rule="evenodd"
clip-rule="evenodd"
>
</path>
</svg>
</button>
<script>
// Toggle early based on <html class="dark">
const themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
const themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
if (themeToggleDarkIcon != null && themeToggleLightIcon != null) {
let dark = document.documentElement.classList.contains('dark');
const show = dark ? themeToggleDarkIcon : themeToggleLightIcon
const hide = dark ? themeToggleLightIcon : themeToggleDarkIcon
show.classList.remove('hidden', 'text-transparent');
hide.classList.add('hidden', 'text-transparent');
}
</script>
"""
end
end

View file

@ -5,7 +5,7 @@
<img src={~p"/images/logo.svg"} width="36" /> <img src={~p"/images/logo.svg"} width="36" />
</a> </a>
<p class="bg-brand/5 text-brand rounded-full px-2 font-medium leading-6"> <p class="bg-brand/5 text-brand rounded-full px-2 font-medium leading-6">
v{Application.spec(:exmr, :vsn)} v<%= Application.spec(:exmr, :vsn) %>
</p> </p>
</div> </div>
</div> </div>
@ -13,6 +13,6 @@
<main class="px-4 py-20 sm:px-6 lg:px-8"> <main class="px-4 py-20 sm:px-6 lg:px-8">
<div class="mx-auto max-w-2xl"> <div class="mx-auto max-w-2xl">
<.flash_group flash={@flash} /> <.flash_group flash={@flash} />
{@inner_content} <%= @inner_content %>
</div> </div>
</main> </main>

View file

@ -4,63 +4,55 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="csrf-token" content={get_csrf_token()} /> <meta name="csrf-token" content={get_csrf_token()} />
<.live_title> <.live_title suffix=" · Phoenix Framework">
{assigns[:page_title] || "ExMR"} <%= assigns[:page_title] || "Exmr" %>
</.live_title> </.live_title>
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} /> <link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
<script>
if (localStorage.getItem('theme') === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark')
}
</script>
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}> <script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
</script> </script>
</head> </head>
<body class="bg-white dark:bg-zinc-950 dark:text-zinc-100"> <body class="bg-white">
<ul class="relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end"> <ul class="relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">
<DarkMode.button />
<%= if @current_user do %> <%= if @current_user do %>
<li class="text-[0.8125rem] leading-6 text-zinc-900 dark:text-zinc-300"> <li class="text-[0.8125rem] leading-6 text-zinc-900">
{@current_user.email} <%= @current_user.email %>
</li> </li>
<li> <li>
<.link <.link
href={~p"/users/settings"} href={~p"/users/settings"}
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700 dark:text-zinc-300 dark:hover:text-zinc-100" class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
> >
{gettext("Settings")} Settings
</.link> </.link>
</li> </li>
<li> <li>
<.link <.link
href={~p"/users/log_out"} href={~p"/users/log_out"}
method="delete" method="delete"
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700 dark:text-zinc-300 dark:hover:text-zinc-100" class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
> >
{gettext("Log out")} Log out
</.link> </.link>
</li> </li>
<% else %> <% else %>
<li> <li>
<.link <.link
href={~p"/users/register"} href={~p"/users/register"}
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700 dark:text-zinc-300 dark:hover:text-zinc-100" class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
> >
{gettext("Register")} Register
</.link> </.link>
</li> </li>
<li> <li>
<.link <.link
href={~p"/users/log_in"} href={~p"/users/log_in"}
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700 dark:text-zinc-300 dark:hover:text-zinc-100" class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
> >
{gettext("Log in")} Log in
</.link> </.link>
</li> </li>
<% end %> <% end %>
</ul> </ul>
{@inner_content} <%= @inner_content %>
</body> </body>
</html> </html>

View file

@ -44,25 +44,25 @@
<h1 class="mt-10 flex items-center text-sm font-semibold leading-6 text-purple-400"> <h1 class="mt-10 flex items-center text-sm font-semibold leading-6 text-purple-400">
ExMR ExMR
<small class="bg-brand/5 text-[0.8125rem] ml-3 rounded-full px-2 font-medium leading-6 text-purple-400"> <small class="bg-brand/5 text-[0.8125rem] ml-3 rounded-full px-2 font-medium leading-6 text-purple-400">
v{Application.spec(:exmr, :vsn)} v<%= Application.spec(:exmr, :vsn) %>
</small> </small>
</h1> </h1>
<p class="text-[2rem] mt-4 font-semibold leading-10 tracking-tighter text-zinc-900 dark:text-zinc-200/90 text-balance"> <p class="text-[2rem] mt-4 font-semibold leading-10 tracking-tighter text-zinc-900 text-balance">
{gettext("A simple, modern, and fast exam management system.")} hell yeah
</p> </p>
<p class="mt-4 text-base leading-7 text-zinc-600 dark:text-zinc-300"> <p class="mt-4 text-base leading-7 text-zinc-600">
{gettext("Built using Phoenix LiveView, Ecto, and TailwindCSS by @vavakado")} y'all really though i'm gonna be serious? lol
</p> </p>
<div class="flex"> <div class="flex">
<div class="w-full sm:w-auto"> <div class="w-full sm:w-auto">
<div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-3"> <div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-3">
<a <a
href="https://git.vavakado.xyz/vavakado/exmr" href="https://github.com/phoenixframework/phoenix"
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6" class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6"
> >
<span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 dark:bg-zinc-800 dark:group-hover:bg-zinc-900/95 sm:group-hover:scale-105"> <span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 sm:group-hover:scale-105">
</span> </span>
<span class="relative flex items-center gap-4 sm:flex-col dark:text-white"> <span class="relative flex items-center gap-4 sm:flex-col">
<svg viewBox="0 0 24 24" aria-hidden="true" class="h-6 w-6"> <svg viewBox="0 0 24 24" aria-hidden="true" class="h-6 w-6">
<path <path
fill-rule="evenodd" fill-rule="evenodd"
@ -79,9 +79,9 @@
href="https://mas.to/@vavakado" href="https://mas.to/@vavakado"
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6" class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6"
> >
<span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 sm:group-hover:scale-105 dark:bg-zinc-800 dark:group-hover:bg-zinc-900/95"> <span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 sm:group-hover:scale-105">
</span> </span>
<span class="relative flex items-center gap-4 sm:flex-col dark:text-white"> <span class="relative flex items-center gap-4 sm:flex-col">
<svg viewBox="0 0 24 24" aria-hidden="true" class="h-6 w-6"> <svg viewBox="0 0 24 24" aria-hidden="true" class="h-6 w-6">
<path d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a4 4 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522q0-1.288.66-2.046c.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764q.662.757.661 2.046z" /> <path d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a4 4 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522q0-1.288.66-2.046c.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764q.662.757.661 2.046z" />
</svg> </svg>
@ -93,7 +93,7 @@
</div> </div>
<div class="text-center py-8 font-bold text-3xl text-red-600"> <div class="text-center py-8 font-bold text-3xl text-red-600">
<.link navigate={~p"/exams"} class="underline hover:text-red-400 transition-all ease-in-out"> <.link navigate={~p"/exams"} class="underline hover:text-red-400 transition-all ease-in-out">
{gettext("Exams")} exams
</.link> </.link>
</div> </div>
</div> </div>

View file

@ -1,7 +0,0 @@
defmodule ExmrWeb.LiveHelpers do
def on_mount(:default, _params, session, socket) do
locale = session["locale"] || "en"
Gettext.put_locale(locale)
{:cont, socket}
end
end

View file

@ -8,10 +8,8 @@ defmodule ExmrWeb.ExamLive.FormComponent do
~H""" ~H"""
<div> <div>
<.header> <.header>
{@title} <%= @title %>
<:subtitle> <:subtitle>Use this form to manage exam records in your database.</:subtitle>
{gettext("Use this form to manage exam records in your database.")}
</:subtitle>
</.header> </.header>
<.simple_form <.simple_form
@ -21,11 +19,11 @@ defmodule ExmrWeb.ExamLive.FormComponent do
phx-change="validate" phx-change="validate"
phx-submit="save" phx-submit="save"
> >
<.input field={@form[:subject]} type="text" label={gettext("Subject")} /> <.input field={@form[:subject]} type="text" label="Subject" />
<.input field={@form[:description]} type="text" label={gettext("Description")} /> <.input field={@form[:description]} type="text" label="Description" />
<.input field={@form[:date]} type="date" label={gettext("Date")} /> <.input field={@form[:date]} type="date" label="Date" />
<:actions> <:actions>
<.button phx-disable-with={gettext("Saving...")}>{gettext("Save Exam")}</.button> <.button phx-disable-with="Saving...">Save Exam</.button>
</:actions> </:actions>
</.simple_form> </.simple_form>
</div> </div>

View file

@ -12,8 +12,6 @@ defmodule ExmrWeb.ExamLive.Index do
|> assign(:sort_by, "date") |> assign(:sort_by, "date")
|> assign(:live_action, :index) |> assign(:live_action, :index)
|> assign(exam: %{}) |> assign(exam: %{})
|> assign(events: Enum.group_by(Exams.list_exams(), & &1.date))
|> assign(current_month: Date.utc_today())
{:ok, socket} {:ok, socket}
end end
@ -31,7 +29,7 @@ defmodule ExmrWeb.ExamLive.Index do
defp apply_action(socket, :new, _params) do defp apply_action(socket, :new, _params) do
socket socket
|> assign(:page_title, gettext("New Exam")) |> assign(:page_title, "New Exam")
|> assign(:exam, %Exam{}) |> assign(:exam, %Exam{})
end end
@ -46,7 +44,6 @@ defmodule ExmrWeb.ExamLive.Index do
socket = socket =
socket socket
|> assign(:exams, [exam | socket.assigns.exams]) |> assign(:exams, [exam | socket.assigns.exams])
|> assign(events: Enum.group_by(Exams.list_exams(), & &1.date))
{:noreply, socket} {:noreply, socket}
end end
@ -61,7 +58,6 @@ defmodule ExmrWeb.ExamLive.Index do
|> update(:exams, fn exams -> |> update(:exams, fn exams ->
Enum.reject(exams, fn l -> l.id == exam.id end) Enum.reject(exams, fn l -> l.id == exam.id end)
end) end)
|> assign(events: Enum.group_by(Exams.list_exams(), & &1.date))
{:noreply, socket} {:noreply, socket}
end end

View file

@ -1,27 +1,14 @@
<.header> <.header>
{gettext("Listing Exams")} Listing Exams
<div> <div>
<button <button phx-click="sort" phx-value-by="subject" class="font-light">Sort by Subject</button>
phx-click="sort" <a>|</a>
phx-value-by="subject"
class="font-light text-xs md:text-sm lg:text-base" <button phx-click="sort" phx-value-by="date" class="font-light">Sort by Date</button>
>
{gettext("Sort by Subject")}
</button>
<a class="invisible lg:visible">|</a>
<button
phx-click="sort"
phx-value-by="date"
class="font-light text-xs md:text-sm lg:text-base"
>
{gettext("Sort by Date")}
</button>
</div> </div>
<:actions> <:actions>
<.link patch={~p"/exams/new"}> <.link patch={~p"/exams/new"}>
<.button class="dark:bg-sky-300 text-white dark:text-black/80 dark:hover:bg-sky-400"> <.button>New Exam</.button>
{gettext("New Exam")}
</.button>
</.link> </.link>
</:actions> </:actions>
</.header> </.header>
@ -29,32 +16,22 @@
<div class="divide-y"> <div class="divide-y">
<div :for={exam <- @exams} class="py-2 flex gap-2"> <div :for={exam <- @exams} class="py-2 flex gap-2">
<div class="grow"> <div class="grow">
<div class="font-bold text-xs md:text-sm lg:text-base">{exam.subject}</div> <div class="font-bold"><%= exam.subject %></div>
<div class="text-xs md:text-sm lg:text-base"> <div class="font-sm"><%= exam.date %></div>
{exam.date} |
<b class="text-xs md:text-sm lg:text-base">
{case Date.diff(exam.date, Date.utc_today()) do
0 -> gettext("Today")
1 -> gettext("Tommorow")
x when x > 1 -> ngettext("Tomorrow", "%{count} days left", x)
x when x < 0 -> ngettext("Yesterday", "%{count} days passed", x * -1)
end}
</b>
</div>
</div> </div>
<button <button
phx-click="edit" phx-click="edit"
phx-value-id={exam.id} phx-value-id={exam.id}
class="bg-blue-500 hover:bg-blue-700 text-white font-bold p-1 text-xs md:text-sm lg:py-2 lg:px-3 rounded-md" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-3 rounded-md"
> >
{gettext("Edit")} Edit
</button> </button>
<button <button
phx-click="remove" phx-click="remove"
phx-value-id={exam.id} phx-value-id={exam.id}
class="bg-red-500 hover:bg-red-700 text-white font-bold p-1 text-xs md:text-sm lg:py-2 lg:px-3 rounded-md" class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-3 rounded-md"
> >
{gettext("Remove")} Remove
</button> </button>
</div> </div>
</div> </div>
@ -69,53 +46,3 @@
patch={~p"/exams"} patch={~p"/exams"}
/> />
</.modal> </.modal>
<div>
<!-- Navigation -->
<!-- <div class="flex justify-between mb-4"> -->
<!-- <button phx-click="previous_month" class="px-2 py-1 bg-blue-200">Previous</button> -->
<!-- <h2>{@current_month |> Date.to_string() |> String.replace("-", " ")}</h2> -->
<!-- <button phx-click="next_month" class="px-2 py-1 bg-blue-200">Next</button> -->
<!-- </div> -->
<!-- Calendar -->
<div class="grid grid-cols-7 gap-1" id="calendar-container" phx-update="replace">
<!-- Weekdays -->
<%= for weekday <- ~w(#{gettext("Sun")} #{gettext("Mon")} #{gettext("Tue")} #{gettext("Wed")} #{gettext("Thu")}
#{gettext("Fri")} #{gettext("Sat")}) do %>
<div class="font-bold text-center">{weekday}</div>
<% end %>
<!-- Empty spaces for previous month -->
<%= for _ <- 1..(Date.day_of_week(Date.new!(@current_month.year, @current_month.month, 1))) do %>
<div></div>
<% end %>
<!-- Days of the month -->
<%= for day <- 1..Date.days_in_month(@current_month) do %>
<% {:ok, date} = Date.new(@current_month.year, @current_month.month, day) %>
<div
id={"day-#{Date.to_string(date)}"}
class={[
"border rounded p-0.5 md:p-2 lg:p-3 w-full h-12 md:h-20 hover:scale-125 transition-transform ease-in-out duration-100",
@current_month == date &&
"bg-zinc-200 dark:bg-zinc-800 border-green-500 dark:border-green-300",
@current_month != date &&
"bg-white dark:bg-zinc-950 dark:border-zinc-400 "
]}
>
<div class="text-xs md:text-sm lg:text-base font-bold">{day}</div>
<!-- Render events for the current day -->
<%= if Map.has_key?(@events, date) do %>
<ul class="text-xs">
<%= for event <- @events[date] do %>
<li id={"event-#{event.id}"}>{event.subject}</li>
<% end %>
</ul>
<% end %>
</div>
<% end %>
</div>
</div>

View file

@ -1,6 +1,6 @@
<.header> <.header>
exam №{@exam.id} exam №<%= @exam.id %>
<:subtitle>очередная контрольная работа по <strong>{@exam.subject}</strong></:subtitle> <:subtitle>очередная контрольная работа по <strong><%= @exam.subject %></strong></:subtitle>
<:actions> <:actions>
<.link patch={~p"/exams/#{@exam}/show/edit"} phx-click={JS.push_focus()}> <.link patch={~p"/exams/#{@exam}/show/edit"} phx-click={JS.push_focus()}>
<.button>изменить</.button> <.button>изменить</.button>
@ -9,8 +9,8 @@
</.header> </.header>
<.list> <.list>
<:item title="Description">{@exam.description}</:item> <:item title="Description"><%= @exam.description %></:item>
<:item title="Date">{@exam.date}</:item> <:item title="Date"><%= @exam.date %></:item>
</.list> </.list>
<.back navigate={~p"/exams"}>Back to exams</.back> <.back navigate={~p"/exams"}>Back to exams</.back>

View file

@ -14,7 +14,7 @@ defmodule ExmrWeb.UserForgotPasswordLive do
<.simple_form for={@form} id="reset_password_form" phx-submit="send_email"> <.simple_form for={@form} id="reset_password_form" phx-submit="send_email">
<.input field={@form[:email]} type="email" placeholder="Email" required /> <.input field={@form[:email]} type="email" placeholder="Email" required />
<:actions> <:actions>
<.button phx-disable-with="Sending..." class="w-full dark:text-black dark:bg-red-600"> <.button phx-disable-with="Sending..." class="w-full">
Send password reset instructions Send password reset instructions
</.button> </.button>
</:actions> </:actions>

View file

@ -26,7 +26,7 @@ defmodule ExmrWeb.UserLoginLive do
</.link> </.link>
</:actions> </:actions>
<:actions> <:actions>
<.button phx-disable-with="Logging in..." class="w-full dark:bg-teal-600"> <.button phx-disable-with="Logging in..." class="w-full">
Log in <span aria-hidden="true"></span> Log in <span aria-hidden="true"></span>
</.button> </.button>
</:actions> </:actions>

View file

@ -6,7 +6,7 @@ defmodule ExmrWeb.UserRegistrationLive do
def render(assigns) do def render(assigns) do
~H""" ~H"""
<div class="mx-auto max-w-sm dark:bg-zinc-950"> <div class="mx-auto max-w-sm">
<.header class="text-center"> <.header class="text-center">
Register for an account Register for an account
<:subtitle> <:subtitle>
@ -35,9 +35,7 @@ defmodule ExmrWeb.UserRegistrationLive do
<.input field={@form[:password]} type="password" label="Password" required /> <.input field={@form[:password]} type="password" label="Password" required />
<:actions> <:actions>
<.button phx-disable-with="Creating account..." class="w-full dark:bg-green-800"> <.button phx-disable-with="Creating account..." class="w-full">Create an account</.button>
Create an account
</.button>
</:actions> </:actions>
</.simple_form> </.simple_form>
</div> </div>

View file

@ -6,8 +6,8 @@ defmodule ExmrWeb.UserSettingsLive do
def render(assigns) do def render(assigns) do
~H""" ~H"""
<.header class="text-center"> <.header class="text-center">
{gettext("Account Settings")} Account Settings
<:subtitle>{gettext("Manage your account email address and password settings")}</:subtitle> <:subtitle>Manage your account email address and password settings</:subtitle>
</.header> </.header>
<div class="space-y-12 divide-y"> <div class="space-y-12 divide-y">
@ -18,20 +18,18 @@ defmodule ExmrWeb.UserSettingsLive do
phx-submit="update_email" phx-submit="update_email"
phx-change="validate_email" phx-change="validate_email"
> >
<.input field={@email_form[:email]} type="email" label={gettext("Email")} required /> <.input field={@email_form[:email]} type="email" label="Email" required />
<.input <.input
field={@email_form[:current_password]} field={@email_form[:current_password]}
name="current_password" name="current_password"
id="current_password_for_email" id="current_password_for_email"
type="password" type="password"
label={gettext("Current password")} label="Current password"
value={@email_form_current_password} value={@email_form_current_password}
required required
/> />
<:actions> <:actions>
<.button phx-disable-with={gettext("Changing...")}> <.button phx-disable-with="Changing...">Change Email</.button>
{gettext("Change Email")}
</.button>
</:actions> </:actions>
</.simple_form> </.simple_form>
</div> </div>
@ -51,30 +49,23 @@ defmodule ExmrWeb.UserSettingsLive do
id="hidden_user_email" id="hidden_user_email"
value={@current_email} value={@current_email}
/> />
<.input <.input field={@password_form[:password]} type="password" label="New password" required />
field={@password_form[:password]}
type="password"
label={gettext("New password")}
required
/>
<.input <.input
field={@password_form[:password_confirmation]} field={@password_form[:password_confirmation]}
type="password" type="password"
label={gettext("Confirm new password")} label="Confirm new password"
/> />
<.input <.input
field={@password_form[:current_password]} field={@password_form[:current_password]}
name="current_password" name="current_password"
type="password" type="password"
label={gettext("Current password")} label="Current password"
id="current_password_for_password" id="current_password_for_password"
value={@current_password} value={@current_password}
required required
/> />
<:actions> <:actions>
<.button phx-disable-with={gettext("Changing...")}> <.button phx-disable-with="Changing...">Change Password</.button>
{gettext("Change Password")}
</.button>
</:actions> </:actions>
</.simple_form> </.simple_form>
</div> </div>
@ -86,10 +77,10 @@ defmodule ExmrWeb.UserSettingsLive do
socket = socket =
case Users.update_user_email(socket.assigns.current_user, token) do case Users.update_user_email(socket.assigns.current_user, token) do
:ok -> :ok ->
put_flash(socket, :info, gettext("Email changed successfully.")) put_flash(socket, :info, "Email changed successfully.")
:error -> :error ->
put_flash(socket, :error, gettext("Email change link is invalid or it has expired.")) put_flash(socket, :error, "Email change link is invalid or it has expired.")
end end
{:ok, push_navigate(socket, to: ~p"/users/settings")} {:ok, push_navigate(socket, to: ~p"/users/settings")}
@ -136,7 +127,7 @@ defmodule ExmrWeb.UserSettingsLive do
&url(~p"/users/settings/confirm_email/#{&1}") &url(~p"/users/settings/confirm_email/#{&1}")
) )
info = gettext("A link to confirm your email change has been sent to the new address.") info = "A link to confirm your email change has been sent to the new address."
{:noreply, socket |> put_flash(:info, info) |> assign(email_form_current_password: nil)} {:noreply, socket |> put_flash(:info, info) |> assign(email_form_current_password: nil)}
{:error, changeset} -> {:error, changeset} ->

View file

@ -1,67 +0,0 @@
defmodule ExmrWeb.Plugs.Locale do
import Plug.Conn
def init(_opts), do: nil
def call(conn, _opts) do
accepted_languages = extract_accept_language(conn)
known_locales = Gettext.known_locales(ExmrWeb.Gettext)
accepted_languages =
known_locales --
known_locales -- accepted_languages
case accepted_languages do
[locale | _] ->
Gettext.put_locale(ExmrWeb.Gettext, locale)
conn
|> put_session(:locale, locale)
_ ->
conn
end
end
# Copied from
# https://raw.githubusercontent.com/smeevil/set_locale/fd35624e25d79d61e70742e42ade955e5ff857b8/lib/headers.ex
def extract_accept_language(conn) do
case Plug.Conn.get_req_header(conn, "accept-language") do
[value | _] ->
value
|> String.split(",")
|> Enum.map(&parse_language_option/1)
|> Enum.sort(&(&1.quality > &2.quality))
|> Enum.map(& &1.tag)
|> Enum.reject(&is_nil/1)
|> ensure_language_fallbacks()
_ ->
[]
end
end
defp parse_language_option(string) do
captures = Regex.named_captures(~r/^\s?(?<tag>[\w\-]+)(?:;q=(?<quality>[\d\.]+))?$/i, string)
quality =
case Float.parse(captures["quality"] || "1.0") do
{val, _} -> val
_ -> 1.0
end
%{tag: captures["tag"], quality: quality}
end
defp ensure_language_fallbacks(tags) do
Enum.flat_map(tags, fn tag ->
case String.split(tag, "-") do
[language, _country_variant] ->
if Enum.member?(tags, language), do: [tag], else: [tag, language]
[_language] ->
[tag]
end
end)
end
end

View file

@ -12,7 +12,6 @@ defmodule ExmrWeb.Router do
plug :put_root_layout, html: {ExmrWeb.Layouts, :root} plug :put_root_layout, html: {ExmrWeb.Layouts, :root}
plug :protect_from_forgery plug :protect_from_forgery
plug :put_secure_browser_headers plug :put_secure_browser_headers
plug ExmrWeb.Plugs.Locale
plug :fetch_current_user plug :fetch_current_user
end end
@ -54,7 +53,7 @@ defmodule ExmrWeb.Router do
pipe_through [:browser, :redirect_if_user_is_authenticated] pipe_through [:browser, :redirect_if_user_is_authenticated]
live_session :redirect_if_user_is_authenticated, live_session :redirect_if_user_is_authenticated,
on_mount: [{ExmrWeb.UserAuth, :redirect_if_user_is_authenticated}, ExmrWeb.LiveHelpers] do on_mount: [{ExmrWeb.UserAuth, :redirect_if_user_is_authenticated}] do
if Exmr.enable_registration() != "false" do if Exmr.enable_registration() != "false" do
live "/users/register", UserRegistrationLive, :new live "/users/register", UserRegistrationLive, :new
end end
@ -71,7 +70,7 @@ defmodule ExmrWeb.Router do
pipe_through [:browser, :require_authenticated_user] pipe_through [:browser, :require_authenticated_user]
live_session :require_authenticated_user, live_session :require_authenticated_user,
on_mount: [{ExmrWeb.UserAuth, :ensure_authenticated}, ExmrWeb.LiveHelpers] do on_mount: [{ExmrWeb.UserAuth, :ensure_authenticated}] do
if Exmr.enable_registration() == "false" do if Exmr.enable_registration() == "false" do
live "/users/register", UserRegistrationLive, :new live "/users/register", UserRegistrationLive, :new
end end
@ -94,7 +93,7 @@ defmodule ExmrWeb.Router do
delete "/users/log_out", UserSessionController, :delete delete "/users/log_out", UserSessionController, :delete
live_session :current_user, live_session :current_user,
on_mount: [{ExmrWeb.UserAuth, :mount_current_user}, ExmrWeb.LiveHelpers] do on_mount: [{ExmrWeb.UserAuth, :mount_current_user}] do
live "/users/confirm/:token", UserConfirmationLive, :edit live "/users/confirm/:token", UserConfirmationLive, :edit
live "/users/confirm", UserConfirmationInstructionsLive, :new live "/users/confirm", UserConfirmationInstructionsLive, :new
end end

View file

@ -4,7 +4,7 @@ defmodule Exmr.MixProject do
def project do def project do
[ [
app: :exmr, app: :exmr,
version: "0.2.2", version: "0.1.0",
elixir: "~> 1.14", elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()), elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod, start_permanent: Mix.env() == :prod,
@ -40,7 +40,8 @@ defmodule Exmr.MixProject do
{:credo, "~> 1.7", only: [:dev, :test], runtime: false}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:phoenix_html, "~> 4.1"}, {:phoenix_html, "~> 4.1"},
{:phoenix_live_reload, "~> 1.2", only: :dev}, {:phoenix_live_reload, "~> 1.2", only: :dev},
{:phoenix_live_view, "~> 1.0.0"}, # TODO bump on release to {:phoenix_live_view, "~> 1.0.0"},
{:phoenix_live_view, "~> 1.0.0-rc.1", override: true},
{:floki, ">= 0.30.0", only: :test}, {:floki, ">= 0.30.0", only: :test},
{:phoenix_live_dashboard, "~> 0.8.3"}, {:phoenix_live_dashboard, "~> 0.8.3"},
{:esbuild, "~> 0.8", runtime: Mix.env() == :dev}, {:esbuild, "~> 0.8", runtime: Mix.env() == :dev},

View file

@ -1,5 +1,5 @@
%{ %{
"bandit": {:hex, :bandit, "1.6.1", "9e01b93d72ddc21d8c576a704949e86ee6cde7d11270a1d3073787876527a48f", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5a904bf010ea24b67979835e0507688e31ac873d4ffc8ed0e5413e8d77455031"}, "bandit": {:hex, :bandit, "1.5.7", "6856b1e1df4f2b0cb3df1377eab7891bec2da6a7fd69dc78594ad3e152363a50", [:mix], [{:hpax, "~> 1.0.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "f2dd92ae87d2cbea2fa9aa1652db157b6cba6c405cb44d4f6dd87abba41371cd"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.2.0", "feab711974beba4cb348147170346fe097eea2e840db4e012a145e180ed4ab75", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "563e92a6c77d667b19c5f4ba17ab6d440a085696bdf4c68b9b0f5b30bc5422b8"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.2.0", "feab711974beba4cb348147170346fe097eea2e840db4e012a145e180ed4ab75", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "563e92a6c77d667b19c5f4ba17ab6d440a085696bdf4c68b9b0f5b30bc5422b8"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"castore": {:hex, :castore, "1.0.10", "43bbeeac820f16c89f79721af1b3e092399b3a1ecc8df1a472738fd853574911", [:mix], [], "hexpm", "1b0b7ea14d889d9ea21202c43a4fa015eb913021cb535e8ed91946f4b77a8848"}, "castore": {:hex, :castore, "1.0.10", "43bbeeac820f16c89f79721af1b3e092399b3a1ecc8df1a472738fd853574911", [:mix], [], "hexpm", "1b0b7ea14d889d9ea21202c43a4fa015eb913021cb535e8ed91946f4b77a8848"},
@ -15,21 +15,21 @@
"expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"}, "expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"},
"file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
"finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"},
"floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"}, "floki": {:hex, :floki, "0.36.3", "1102f93b16a55bc5383b85ae3ec470f82dee056eaeff9195e8afdf0ef2a43c30", [:mix], [], "hexpm", "fe0158bff509e407735f6d40b3ee0d7deb47f3f3ee7c6c182ad28599f9f6b27a"},
"gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"}, "gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"},
"heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized", depth: 1]}, "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized", depth: 1]},
"hpax": {:hex, :hpax, "1.0.1", "c857057f89e8bd71d97d9042e009df2a42705d6d690d54eca84c8b29af0787b0", [:mix], [], "hexpm", "4e2d5a4f76ae1e3048f35ae7adb1641c36265510a2d4638157fbcb53dda38445"}, "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"phoenix": {:hex, :phoenix, "1.7.18", "5310c21443514be44ed93c422e15870aef254cf1b3619e4f91538e7529d2b2e4", [: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.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "1797fcc82108442a66f2c77a643a62980f342bfeb63d6c9a515ab8294870004e"}, "phoenix": {:hex, :phoenix, "1.7.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [: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.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.6.3", "f686701b0499a07f2e3b122d84d52ff8a31f5def386e03706c916f6feddf69ef", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "909502956916a657a197f94cc1206d9a65247538de8a5e186f7537c895d95764"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.3", "f686701b0499a07f2e3b122d84d52ff8a31f5def386e03706c916f6feddf69ef", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "909502956916a657a197f94cc1206d9a65247538de8a5e186f7537c895d95764"},
"phoenix_html": {:hex, :phoenix_html, "4.1.1", "4c064fd3873d12ebb1388425a8f2a19348cef56e7289e1998e2d2fa758aa982e", [:mix], [], "hexpm", "f2f2df5a72bc9a2f510b21497fd7d2b86d932ec0598f0210fed4114adc546c6f"}, "phoenix_html": {:hex, :phoenix_html, "4.1.1", "4c064fd3873d12ebb1388425a8f2a19348cef56e7289e1998e2d2fa758aa982e", [:mix], [], "hexpm", "f2f2df5a72bc9a2f510b21497fd7d2b86d932ec0598f0210fed4114adc546c6f"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.5", "d5f44d7dbd7cfacaa617b70c5a14b2b598d6f93b9caa8e350c51d56cd4350a9b", [: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]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1d73920515554d7d6c548aee0bf10a4780568b029d042eccb336db29ea0dad70"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.5", "d5f44d7dbd7cfacaa617b70c5a14b2b598d6f93b9caa8e350c51d56cd4350a9b", [: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]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1d73920515554d7d6c548aee0bf10a4780568b029d042eccb336db29ea0dad70"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"},
"phoenix_live_view": {:hex, :phoenix_live_view, "1.0.0", "3a10dfce8f87b2ad4dc65de0732fc2a11e670b2779a19e8d3281f4619a85bce4", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {: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 or ~> 4.0", [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]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "254caef0028765965ca6bd104cc7d68dcc7d57cc42912bef92f6b03047251d99"}, "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.0-rc.7", "d2abca526422adea88896769529addb6443390b1d4f1ff9cbe694312d8875fb2", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {: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 or ~> 4.0", [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]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b82a4575f6f3eb5b97922ec6874b0c52b3ca0cc5dcb4b14ddc478cbfa135dd01"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
"plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
@ -40,7 +40,7 @@
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"telemetry_metrics": {:hex, :telemetry_metrics, "1.0.0", "29f5f84991ca98b8eb02fc208b2e6de7c95f8bb2294ef244a176675adc7775df", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f23713b3847286a534e005126d4c959ebcca68ae9582118ce436b521d1d47d5d"}, "telemetry_metrics": {:hex, :telemetry_metrics, "1.0.0", "29f5f84991ca98b8eb02fc208b2e6de7c95f8bb2294ef244a176675adc7775df", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f23713b3847286a534e005126d4c959ebcca68ae9582118ce436b521d1d47d5d"},
"telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"}, "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"},
"thousand_island": {:hex, :thousand_island, "1.3.7", "1da7598c0f4f5f50562c097a3f8af308ded48cd35139f0e6f17d9443e4d0c9c5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0139335079953de41d381a6134d8b618d53d084f558c734f2662d1a72818dd12"}, "thousand_island": {:hex, :thousand_island, "1.3.5", "6022b6338f1635b3d32406ff98d68b843ba73b3aa95cfc27154223244f3a6ca5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2be6954916fdfe4756af3239fb6b6d75d0b8063b5df03ba76fd8a4c87849e180"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
"websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"},
} }

View file

@ -1,270 +0,0 @@
## This file is a PO Template file.
##
## "msgid"s here are often extracted from source code.
## Add new messages manually only if they're dynamic
## messages that can't be statically extracted.
##
## Run "mix gettext.extract" to bring this file up to
## date. Leave "msgstr"s empty as changing them here has no
## effect: edit them in PO (.po) files instead.
#
msgid ""
msgstr ""
#: lib/exmr_web/controllers/page_html/home.html.heex:51
#, elixir-autogen, elixir-format
msgid "A simple, modern, and fast exam management system."
msgstr ""
#: lib/exmr_web/components/core_components.ex:489
#, elixir-autogen, elixir-format
msgid "Actions"
msgstr ""
#: lib/exmr_web/components/core_components.ex:164
#, elixir-autogen, elixir-format
msgid "Attempting to reconnect"
msgstr ""
#: lib/exmr_web/controllers/page_html/home.html.heex:54
#, elixir-autogen, elixir-format
msgid "Built using Phoenix LiveView, Ecto, and TailwindCSS by @vavakado"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:50
#, elixir-autogen, elixir-format
msgid "Edit"
msgstr ""
#: lib/exmr_web/components/core_components.ex:155
#, elixir-autogen, elixir-format
msgid "Error!"
msgstr ""
#: lib/exmr_web/controllers/page_html/home.html.heex:96
#, elixir-autogen, elixir-format
msgid "Exams"
msgstr ""
#: lib/exmr_web/components/core_components.ex:176
#, elixir-autogen, elixir-format
msgid "Hang in there while we get back on track"
msgstr ""
#: lib/exmr_web/live/exam_live/index.ex:34
#: lib/exmr_web/live/exam_live/index.html.heex:23
#, elixir-autogen, elixir-format
msgid "New Exam"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:57
#, elixir-autogen, elixir-format
msgid "Remove"
msgstr ""
#: lib/exmr_web/components/core_components.ex:171
#, elixir-autogen, elixir-format
msgid "Something went wrong!"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:17
#, elixir-autogen, elixir-format
msgid "Sort by Date"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:9
#, elixir-autogen, elixir-format
msgid "Sort by Subject"
msgstr ""
#: lib/exmr_web/components/core_components.ex:154
#, elixir-autogen, elixir-format
msgid "Success!"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:37
#, elixir-autogen, elixir-format
msgid "Today"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:39
#, elixir-autogen, elixir-format
msgid "Tomorrow"
msgid_plural "%{count} days left"
msgstr[0] ""
msgstr[1] ""
#: lib/exmr_web/components/core_components.ex:159
#, elixir-autogen, elixir-format
msgid "We can't find the internet"
msgstr ""
#: lib/exmr_web/components/core_components.ex:80
#: lib/exmr_web/components/core_components.ex:134
#, elixir-autogen, elixir-format
msgid "close"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:139
#, elixir-autogen, elixir-format
msgid "A link to confirm your email change has been sent to the new address."
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:9
#, elixir-autogen, elixir-format
msgid "Account Settings"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:33
#, elixir-autogen, elixir-format
msgid "Change Email"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:76
#, elixir-autogen, elixir-format
msgid "Change Password"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:32
#: lib/exmr_web/live/user_settings_live.ex:75
#, elixir-autogen, elixir-format
msgid "Changing..."
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:63
#, elixir-autogen, elixir-format
msgid "Confirm new password"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:27
#: lib/exmr_web/live/user_settings_live.ex:69
#, elixir-autogen, elixir-format
msgid "Current password"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:21
#, elixir-autogen, elixir-format
msgid "Email"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:92
#, elixir-autogen, elixir-format
msgid "Email change link is invalid or it has expired."
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:89
#, elixir-autogen, elixir-format
msgid "Email changed successfully."
msgstr ""
#: lib/exmr_web/components/layouts/root.html.heex:42
#, elixir-autogen, elixir-format
msgid "Log out"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:10
#, elixir-autogen, elixir-format
msgid "Manage your account email address and password settings"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:57
#, elixir-autogen, elixir-format
msgid "New password"
msgstr ""
#: lib/exmr_web/components/layouts/root.html.heex:33
#, elixir-autogen, elixir-format
msgid "Settings"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:26
#, elixir-autogen, elixir-format
msgid "Date"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:25
#, elixir-autogen, elixir-format
msgid "Description"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:24
#, elixir-autogen, elixir-format
msgid "Subject"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:13
#, elixir-autogen, elixir-format
msgid "Use this form to manage exam records in your database."
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:2
#, elixir-autogen, elixir-format
msgid "Listing Exams"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:28
#, elixir-autogen, elixir-format
msgid "Save Exam"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:28
#, elixir-autogen, elixir-format
msgid "Saving..."
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:40
#, elixir-autogen, elixir-format
msgid "Yesterday"
msgid_plural "%{count} days passed"
msgstr[0] ""
msgstr[1] ""
#: lib/exmr_web/live/exam_live/index.html.heex:38
#, elixir-autogen, elixir-format
msgid "Tommorow"
msgstr ""
#: lib/exmr_web/components/layouts/root.html.heex:59
#, elixir-autogen, elixir-format
msgid "Log in"
msgstr ""
#: lib/exmr_web/components/layouts/root.html.heex:51
#, elixir-autogen, elixir-format
msgid "Register"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:85
#, elixir-autogen, elixir-format
msgid "Fri"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:84
#, elixir-autogen, elixir-format
msgid "Mon"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:85
#, elixir-autogen, elixir-format
msgid "Sat"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:84
#, elixir-autogen, elixir-format
msgid "Sun"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:84
#, elixir-autogen, elixir-format
msgid "Thu"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:84
#, elixir-autogen, elixir-format
msgid "Tue"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:84
#, elixir-autogen, elixir-format
msgid "Wed"
msgstr ""

View file

@ -1,270 +0,0 @@
## "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: en\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: lib/exmr_web/controllers/page_html/home.html.heex:51
#, elixir-autogen, elixir-format
msgid "A simple, modern, and fast exam management system."
msgstr ""
#: lib/exmr_web/components/core_components.ex:489
#, elixir-autogen, elixir-format
msgid "Actions"
msgstr ""
#: lib/exmr_web/components/core_components.ex:164
#, elixir-autogen, elixir-format
msgid "Attempting to reconnect"
msgstr ""
#: lib/exmr_web/controllers/page_html/home.html.heex:54
#, elixir-autogen, elixir-format
msgid "Built using Phoenix LiveView, Ecto, and TailwindCSS by @vavakado"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:50
#, elixir-autogen, elixir-format
msgid "Edit"
msgstr ""
#: lib/exmr_web/components/core_components.ex:155
#, elixir-autogen, elixir-format
msgid "Error!"
msgstr ""
#: lib/exmr_web/controllers/page_html/home.html.heex:96
#, elixir-autogen, elixir-format
msgid "Exams"
msgstr ""
#: lib/exmr_web/components/core_components.ex:176
#, elixir-autogen, elixir-format
msgid "Hang in there while we get back on track"
msgstr ""
#: lib/exmr_web/live/exam_live/index.ex:34
#: lib/exmr_web/live/exam_live/index.html.heex:23
#, elixir-autogen, elixir-format
msgid "New Exam"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:57
#, elixir-autogen, elixir-format
msgid "Remove"
msgstr ""
#: lib/exmr_web/components/core_components.ex:171
#, elixir-autogen, elixir-format
msgid "Something went wrong!"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:17
#, elixir-autogen, elixir-format
msgid "Sort by Date"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:9
#, elixir-autogen, elixir-format
msgid "Sort by Subject"
msgstr ""
#: lib/exmr_web/components/core_components.ex:154
#, elixir-autogen, elixir-format
msgid "Success!"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:37
#, elixir-autogen, elixir-format
msgid "Today"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:39
#, elixir-autogen, elixir-format
msgid "Tomorrow"
msgid_plural "%{count} days left"
msgstr[0] ""
msgstr[1] ""
#: lib/exmr_web/components/core_components.ex:159
#, elixir-autogen, elixir-format
msgid "We can't find the internet"
msgstr ""
#: lib/exmr_web/components/core_components.ex:80
#: lib/exmr_web/components/core_components.ex:134
#, elixir-autogen, elixir-format
msgid "close"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:139
#, elixir-autogen, elixir-format
msgid "A link to confirm your email change has been sent to the new address."
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:9
#, elixir-autogen, elixir-format
msgid "Account Settings"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:33
#, elixir-autogen, elixir-format
msgid "Change Email"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:76
#, elixir-autogen, elixir-format
msgid "Change Password"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:32
#: lib/exmr_web/live/user_settings_live.ex:75
#, elixir-autogen, elixir-format
msgid "Changing..."
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:63
#, elixir-autogen, elixir-format
msgid "Confirm new password"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:27
#: lib/exmr_web/live/user_settings_live.ex:69
#, elixir-autogen, elixir-format
msgid "Current password"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:21
#, elixir-autogen, elixir-format
msgid "Email"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:92
#, elixir-autogen, elixir-format
msgid "Email change link is invalid or it has expired."
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:89
#, elixir-autogen, elixir-format
msgid "Email changed successfully."
msgstr ""
#: lib/exmr_web/components/layouts/root.html.heex:42
#, elixir-autogen, elixir-format
msgid "Log out"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:10
#, elixir-autogen, elixir-format
msgid "Manage your account email address and password settings"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:57
#, elixir-autogen, elixir-format
msgid "New password"
msgstr ""
#: lib/exmr_web/components/layouts/root.html.heex:33
#, elixir-autogen, elixir-format
msgid "Settings"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:26
#, elixir-autogen, elixir-format
msgid "Date"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:25
#, elixir-autogen, elixir-format
msgid "Description"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:24
#, elixir-autogen, elixir-format
msgid "Subject"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:13
#, elixir-autogen, elixir-format
msgid "Use this form to manage exam records in your database."
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:2
#, elixir-autogen, elixir-format
msgid "Listing Exams"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:28
#, elixir-autogen, elixir-format, fuzzy
msgid "Save Exam"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:28
#, elixir-autogen, elixir-format
msgid "Saving..."
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:40
#, elixir-autogen, elixir-format
msgid "Yesterday"
msgid_plural "%{count} days passed"
msgstr[0] ""
msgstr[1] ""
#: lib/exmr_web/live/exam_live/index.html.heex:38
#, elixir-autogen, elixir-format, fuzzy
msgid "Tommorow"
msgstr ""
#: lib/exmr_web/components/layouts/root.html.heex:59
#, elixir-autogen, elixir-format
msgid "Log in"
msgstr ""
#: lib/exmr_web/components/layouts/root.html.heex:51
#, elixir-autogen, elixir-format
msgid "Register"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:85
#, elixir-autogen, elixir-format
msgid "Fri"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:84
#, elixir-autogen, elixir-format
msgid "Mon"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:85
#, elixir-autogen, elixir-format
msgid "Sat"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:84
#, elixir-autogen, elixir-format
msgid "Sun"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:84
#, elixir-autogen, elixir-format
msgid "Thu"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:84
#, elixir-autogen, elixir-format
msgid "Tue"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:84
#, elixir-autogen, elixir-format
msgid "Wed"
msgstr ""

View file

@ -1,242 +0,0 @@
# # This file is a PO Template file.
# #
# # "msgid"s here are often extracted from source code.
# # Add new messages manually only if they're dynamic
# # messages that can't be statically extracted.
# #
# # Run "mix gettext.extract" to bring this file up to
# # date. Leave "msgstr"s empty as changing them here has no
# # effect: edit them in PO (.po) files instead.
# Vladimir Rubin <vavakado@proton.me>, 2024.
#
msgid ""
msgstr ""
"Project-Id-Version: unnamed project\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2024-12-23 19:01+0200\n"
"Last-Translator: Vladimir Rubin <vavakado@proton.me>\n"
"Language-Team: Russian\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Gtranslator 47.1\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
#: lib/exmr_web/controllers/page_html/home.html.heex:51
msgid "A simple, modern, and fast exam management system."
msgstr "Простая, современная и быстрая система управления экзаменами."
#: lib/exmr_web/components/core_components.ex:489
msgid "Actions"
msgstr "Действия"
#: lib/exmr_web/components/core_components.ex:164
msgid "Attempting to reconnect"
msgstr "Попытка восстановить соединение"
#: lib/exmr_web/controllers/page_html/home.html.heex:54
msgid "Built using Phoenix LiveView, Ecto, and TailwindCSS by @vavakado"
msgstr ""
"Создано с использованием Phoenix LiveView, Ecto и TailwindCSS в исполнении "
"@vavakado"
#: lib/exmr_web/live/exam_live/index.html.heex:50
msgid "Edit"
msgstr "Изменить"
#: lib/exmr_web/components/core_components.ex:155
msgid "Error!"
msgstr "Ошибка!"
#: lib/exmr_web/controllers/page_html/home.html.heex:96
msgid "Exams"
msgstr "Экзамены"
#: lib/exmr_web/components/core_components.ex:176
msgid "Hang in there while we get back on track"
msgstr "Держитесь, пока мы не вернемся в строй"
#: lib/exmr_web/live/exam_live/index.ex:34
#: lib/exmr_web/live/exam_live/index.html.heex:23
msgid "New Exam"
msgstr "Новый экзамен"
#: lib/exmr_web/live/exam_live/index.html.heex:57
msgid "Remove"
msgstr "Удалить"
#: lib/exmr_web/components/core_components.ex:171
msgid "Something went wrong!"
msgstr "Что-то пошло не так!"
#: lib/exmr_web/live/exam_live/index.html.heex:17
msgid "Sort by Date"
msgstr "Сортировать по дате"
#: lib/exmr_web/live/exam_live/index.html.heex:9
msgid "Sort by Subject"
msgstr "Сортировать по предмету"
#: lib/exmr_web/components/core_components.ex:154
msgid "Success!"
msgstr "Успех!"
#: lib/exmr_web/live/exam_live/index.html.heex:37
msgid "Today"
msgstr "Сегодня"
#: lib/exmr_web/live/exam_live/index.html.heex:39
msgid "Tomorrow"
msgid_plural "%{count} days left"
msgstr[0] "%{count} день остался"
msgstr[1] "%{count} дня осталось"
msgstr[2] "%{count} дней осталось"
#: lib/exmr_web/components/core_components.ex:159
msgid "We can't find the internet"
msgstr "Мы не можем найти интернет"
#: lib/exmr_web/components/core_components.ex:80
#: lib/exmr_web/components/core_components.ex:134
msgid "close"
msgstr "закрыть"
#: lib/exmr_web/live/user_settings_live.ex:139
msgid "A link to confirm your email change has been sent to the new address."
msgstr ""
"На новый адрес отправлена ссылка для подтверждения изменения электронной "
"почты."
#: lib/exmr_web/live/user_settings_live.ex:9
msgid "Account Settings"
msgstr "Настройки аккаунта"
#: lib/exmr_web/live/user_settings_live.ex:33
msgid "Change Email"
msgstr "Изменить электронную почту"
#: lib/exmr_web/live/user_settings_live.ex:76
msgid "Change Password"
msgstr "Изменить пароль"
#: lib/exmr_web/live/user_settings_live.ex:32
#: lib/exmr_web/live/user_settings_live.ex:75
msgid "Changing..."
msgstr "Меняется..."
#: lib/exmr_web/live/user_settings_live.ex:63
msgid "Confirm new password"
msgstr "Подтвердите новый пароль"
#: lib/exmr_web/live/user_settings_live.ex:27
#: lib/exmr_web/live/user_settings_live.ex:69
msgid "Current password"
msgstr "Текущий пароль"
#: lib/exmr_web/live/user_settings_live.ex:21
msgid "Email"
msgstr "Электронная почта"
#: lib/exmr_web/live/user_settings_live.ex:92
msgid "Email change link is invalid or it has expired."
msgstr ""
"Ссылка для изменения электронной почты недействительна или срок ее действия "
"истек."
#: lib/exmr_web/live/user_settings_live.ex:89
msgid "Email changed successfully."
msgstr "Электронная почта успешно изменена."
#: lib/exmr_web/components/layouts/root.html.heex:42
msgid "Log out"
msgstr "Выйти из системы"
#: lib/exmr_web/live/user_settings_live.ex:10
msgid "Manage your account email address and password settings"
msgstr "Управление адресом электронной почты и настройками пароля"
#: lib/exmr_web/live/user_settings_live.ex:57
msgid "New password"
msgstr "Новый пароль"
#: lib/exmr_web/components/layouts/root.html.heex:33
msgid "Settings"
msgstr "Настройки"
#: lib/exmr_web/live/exam_live/form_component.ex:26
msgid "Date"
msgstr "Дата"
#: lib/exmr_web/live/exam_live/form_component.ex:25
msgid "Description"
msgstr "Описание"
#: lib/exmr_web/live/exam_live/form_component.ex:24
msgid "Subject"
msgstr "Предмет"
#: lib/exmr_web/live/exam_live/form_component.ex:13
msgid "Use this form to manage exam records in your database."
msgstr ""
"Используйте эту форму для управления записями экзаменов в вашей базе данных."
#: lib/exmr_web/live/exam_live/index.html.heex:2
msgid "Listing Exams"
msgstr "Список Экзаменов"
#: lib/exmr_web/live/exam_live/form_component.ex:28
msgid "Save Exam"
msgstr "Сохранить экзамен"
#: lib/exmr_web/live/exam_live/form_component.ex:28
msgid "Saving..."
msgstr "Сохранение..."
#: lib/exmr_web/live/exam_live/index.html.heex:40
msgid "Yesterday"
msgid_plural "%{count} days passed"
msgstr[0] "%{count} день прошёл"
msgstr[1] "%{count} дня прошло"
msgstr[2] "%{count} дней прошло"
#: lib/exmr_web/live/exam_live/index.html.heex:38
msgid "Tommorow"
msgstr "Завтра"
#: lib/exmr_web/components/layouts/root.html.heex:59
msgid "Log in"
msgstr "Зайти в аккаунт"
#: lib/exmr_web/components/layouts/root.html.heex:51
msgid "Register"
msgstr "Зарегестрироваться"
#: lib/exmr_web/live/exam_live/index.html.heex:85
msgid "Fri"
msgstr "Пт"
#: lib/exmr_web/live/exam_live/index.html.heex:84
msgid "Mon"
msgstr "Пн"
#: lib/exmr_web/live/exam_live/index.html.heex:85
msgid "Sat"
msgstr "Сб"
#: lib/exmr_web/live/exam_live/index.html.heex:84
msgid "Sun"
msgstr "Вс"
#: lib/exmr_web/live/exam_live/index.html.heex:84
msgid "Thu"
msgstr "Чт"
#: lib/exmr_web/live/exam_live/index.html.heex:84
msgid "Tue"
msgstr "Вт"
#: lib/exmr_web/live/exam_live/index.html.heex:84
msgid "Wed"
msgstr "Ср"

View file

@ -1,122 +0,0 @@
# # "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.
# Vladimir Rubin <vavakado@proton.me>, 2024.
#
msgid ""
msgstr ""
"Language: ru\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"MIME-Version: 1.0\n"
"Project-Id-Version: unnamed project\n"
"Last-Translator: Vladimir Rubin <vavakado@proton.me>\n"
"Language-Team: Russian\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2024-12-12 18:52+0200\n"
"X-Generator: Gtranslator 47.1\n"
msgid "can't be blank"
msgstr "не может быть пустым"
msgid "has already been taken"
msgstr "уже забрали"
msgid "is invalid"
msgstr "недействителен"
msgid "must be accepted"
msgstr "должен быть принят"
msgid "has invalid format"
msgstr "имеет неправильный формат"
msgid "has an invalid entry"
msgstr "имеет недопустимую запись"
msgid "is reserved"
msgstr "зарезервирован"
msgid "does not match confirmation"
msgstr "не соответствует подтверждению"
msgid "is still associated with this entry"
msgstr "по-прежнему связана с этой записью"
msgid "are still associated with this entry"
msgstr "по-прежнему связаны с этой записью"
msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)"
msgstr[0] "должен иметь %{count} элементов"
msgstr[1] "должен иметь %{count} элемента"
msgstr[2] "должен иметь %{count} элементов"
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
msgstr[0] "должен быть %{count} символов"
msgstr[1] "должен быть %{count} символа"
msgstr[2] "должен быть %{count} символов"
msgid "should be %{count} byte(s)"
msgid_plural "should be %{count} byte(s)"
msgstr[0] "должно быть %{count} байт"
msgstr[1] "должно быть %{count} байта"
msgstr[2] "должно быть %{count} байтов"
msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)"
msgstr[0] "должно быть не менее %{count} элементов"
msgstr[1] "должно быть не менее %{count} элемента"
msgstr[2] "должно быть не менее %{count} элементов"
msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)"
msgstr[0] "должно быть не менее %{count} символов"
msgstr[1] "должно быть не менее %{count} символа"
msgstr[2] "должно быть не менее %{count} символов"
msgid "should be at least %{count} byte(s)"
msgid_plural "should be at least %{count} byte(s)"
msgstr[0] "должно быть хотя бы %{count} байт"
msgstr[1] "должно быть хотя бы %{count} байта"
msgstr[2] "должно быть хотя бы %{count} байтов"
msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)"
msgstr[0] "должно содержать не более %{count} элементов"
msgstr[1] "должно содержать не более %{count} элементов"
msgstr[2] "должно содержать не более %{count} элементов"
msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)"
msgstr[0] "должно быть не более %{count} символа(ов)"
msgstr[1] "должно быть не более %{count} символа(ов)"
msgstr[2] "должно быть не более %{count} символа(ов)"
msgid "should be at most %{count} byte(s)"
msgid_plural "should be at most %{count} byte(s)"
msgstr[0] "должно быть не более %{count} байт(ов)."
msgstr[1] "должно быть не более %{count} байт(ов)."
msgstr[2] "должно быть не более %{count} байт(ов)."
msgid "must be less than %{number}"
msgstr "должно быть меньше, чем %{number}"
msgid "must be greater than %{number}"
msgstr "должно быть больше, чем %{number}"
msgid "must be less than or equal to %{number}"
msgstr "должно быть меньше или равно %{number}"
msgid "must be greater than or equal to %{number}"
msgstr "должно быть больше или равно %{number}"
msgid "must be equal to %{number}"
msgstr "должно быть равно %{number}"