5 Commits

Author SHA1 Message Date
f03037a943 add search to notes
Some checks are pending
continuous-integration/drone/push Build is pending
2022-11-18 23:45:24 -05:00
282d2b7664 remove transition all on input 2022-11-18 23:45:24 -05:00
3dbbb7e21c alias endpoint 2022-11-18 23:45:24 -05:00
b641e96601 display topbar when user is logged out 2022-11-18 23:45:24 -05:00
e0f0e39326 work on notes 2022-11-18 23:45:24 -05:00
113 changed files with 1575 additions and 5936 deletions

View File

@ -16,7 +16,7 @@ steps:
- assets/node_modules/
- name: test
image: elixir:1.14.1-alpine
image: elixir:1.13.4-alpine
environment:
TEST_DATABASE_URL: ecto://postgres:postgres@database/memex_test
HOST: testing.example.tld
@ -29,7 +29,7 @@ steps:
- npm --prefix ./assets ci --progress=false --no-audit --loglevel=error
- npm run --prefix ./assets deploy
- mix do phx.digest, gettext.extract
- mix test.all
- mix test
- name: build and publish stable
image: thegeeklab/drone-docker-buildx
@ -38,7 +38,7 @@ steps:
repo: shibaobun/memex
purge: true
compress: true
platforms: linux/amd64,linux/arm/v7
platforms: linux/amd64,linux/arm64,linux/arm/v7
username:
from_secret: docker_username
password:
@ -55,7 +55,7 @@ steps:
repo: shibaobun/memex
purge: true
compress: true
platforms: linux/amd64,linux/arm/v7
platforms: linux/amd64,linux/arm64,linux/arm/v7
username:
from_secret: docker_username
password:

View File

@ -1,4 +1,4 @@
FROM elixir:1.14.1-alpine AS build
FROM elixir:1.13-alpine AS build
# install build dependencies
RUN apk add --no-cache build-base npm git python3
@ -37,7 +37,7 @@ RUN mix do compile, release
FROM alpine:latest AS app
RUN apk upgrade --no-cache && \
apk add --no-cache bash openssl libssl1.1 libcrypto1.1 libgcc libstdc++ ncurses-libs
apk add --no-cache bash openssl libgcc libstdc++ ncurses-libs
WORKDIR /app

View File

@ -7,7 +7,7 @@
.input-primary {
@apply bg-primary-900;
@apply border-primary-900 hover:border-primary-800 active:border-primary-700;
@apply text-primary-400 placeholder-primary-600;
@apply text-primary-400;
}
.checkbox {
@ -44,7 +44,11 @@
}
.hr {
@apply mx-auto border border-primary-600 w-full max-w-2xl;
@apply border border-primary-400 w-full max-w-2xl;
}
.hr-light {
@apply border border-primary-600 w-full max-w-2xl;
}
.link {

View File

@ -29,9 +29,7 @@ import topbar from '../vendor/topbar'
import MaintainAttrs from './maintain_attrs'
import Alpine from 'alpinejs'
const csrfTokenElement = document.querySelector("meta[name='csrf-token']")
let csrfToken
if (csrfTokenElement) { csrfToken = csrfTokenElement.getAttribute('content') }
const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute('content')
const liveSocket = new LiveSocket('/live', Socket, {
dom: {
onBeforeElUpdated (from, to) {
@ -47,7 +45,7 @@ window.Alpine = Alpine
Alpine.start()
// Show progress bar on live navigation and form submits
topbar.config({ barThickness: 1, barColors: { 0: '#fff' }, 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())
window.addEventListener('phx:page-loading-stop', info => topbar.hide())

View File

@ -1,26 +0,0 @@
# v0.1.5
- fix overflow on note/contexts/step contents
# v0.1.4
- fix docker-compose
- fix newlines in note/context/step contents
- fix user invite page
- improve tagging logic
# v0.1.3
- backlink to other notes in notes
- search tags on click
# v0.1.2
- fix more typos
- add to faq
- check for slug uniqueness before submitting
# v0.1.1
- improve search a whole lot
- improve table information for notes and contexts
- fix some typos
- use project version on homepage
# v0.1.0
- initial release >:3c

View File

@ -62,7 +62,7 @@ if config_env() == :prod do
System.get_env("SECRET_KEY_BASE") ||
raise """
environment variable SECRET_KEY_BASE is missing.
You can generate one by running: mix phx.gen.secret
You can generate one by calling: mix phx.gen.secret
"""
config :memex, MemexWeb.Endpoint, secret_key_base: secret_key_base
@ -79,7 +79,7 @@ if config_env() == :prod do
password: System.get_env("SMTP_PASSWORD") || raise("No SMTP_PASSWORD set!"),
ssl: System.get_env("SMTP_SSL") == "true",
email_from: System.get_env("EMAIL_FROM") || "no-reply@#{System.get_env("HOST")}",
email_name: System.get_env("EMAIL_NAME") || "memEx"
email_name: System.get_env("EMAIL_NAME") || "Memex"
# ## Using releases
#

View File

@ -77,14 +77,14 @@ Check them out!
For development, I recommend setting environment variables with
[direnv](https://direnv.net).
By default, memEx will always bind to all external IPv4 and IPv6 addresses in
By default, Memex will always bind to all external IPv4 and IPv6 addresses in
`dev` and `prod` mode, respectively. If you would like to use different values,
they will need to be overridden in `config/dev.exs` and `config/runtime.exs` for
`dev` and `prod` modes, respectively.
## `MIX_ENV=dev`
In `dev` mode, memEx will listen for these environment variables at runtime.
In `dev` mode, Memex will listen for these environment variables at runtime.
- `HOST`: External url to generate links with. Set this especially if you're
behind a reverse proxy. Defaults to `localhost`. External URLs will always be
@ -100,7 +100,7 @@ In `dev` mode, memEx will listen for these environment variables at runtime.
## `MIX_ENV=test`
In `test` mode (or in the Docker container), memEx will listen for the same environment variables as dev mode, but also include the following at runtime:
In `test` mode (or in the Docker container), Memex will listen for the same environment variables as dev mode, but also include the following at runtime:
- `TEST_DATABASE_URL`: REPLACES `DATABASE_URL`. Controls the database url to
connect to. Defaults to `ecto://postgres:postgres@localhost/memex_test`.
@ -110,7 +110,7 @@ In `test` mode (or in the Docker container), memEx will listen for the same envi
## `MIX_ENV=prod`
In `prod` mode (or in the Docker container), memEx will listen for the same environment variables as dev mode, but also include the following at runtime:
In `prod` mode (or in the Docker container), Memex will listen for the same environment variables as dev mode, but also include the following at runtime:
- `SECRET_KEY_BASE`: Secret key base used to sign cookies. Must be generated
with `docker run -it shibaobun/memex mix phx.gen.secret` and set for server to start.
@ -121,4 +121,4 @@ In `prod` mode (or in the Docker container), memEx will listen for the same envi
- `SMTP_SSL`: Set to `true` to enable SSL for emails. Defaults to `false`.
- `EMAIL_FROM`: Sets the sender email in sent emails. Defaults to
`no-reply@HOST` where `HOST` was previously defined.
- `EMAIL_NAME`: Sets the sender name in sent emails. Defaults to "memEx".
- `EMAIL_NAME`: Sets the sender name in sent emails. Defaults to "Memex".

10
de.tbx
View File

@ -1,10 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE martif PUBLIC "ISO 12200:1999A//DTD MARTIF core (DXFcdV04)//EN" "TBXcdv04.dtd">
<martif type="TBX">
<martifHeader>
<fileDesc>
<sourceDesc><p>Translate Toolkit</p></sourceDesc>
</fileDesc>
</martifHeader>
<text><body></body></text>
</martif>

View File

@ -2,7 +2,8 @@ version: '3'
services:
memex:
image: shibaobun/memex
build:
context: .
container_name: memex
restart: always
environment:
@ -24,8 +25,8 @@ services:
# - SMTP_SSL=false
# optional, default is format below
# - EMAIL_FROM=no-reply@memex.example.tld
# optional, default is "memEx"
# - EMAIL_NAME=memEx
# optional, default is "Memex"
# - EMAIL_NAME=Memex
expose:
- "4000"
depends_on:

BIN
home.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

View File

@ -23,7 +23,7 @@ defmodule Memex.Accounts do
nil
"""
@spec get_user_by_email(email :: String.t()) :: User.t() | nil
@spec get_user_by_email(String.t()) :: User.t() | nil
def get_user_by_email(email) when is_binary(email), do: Repo.get_by(User, email: email)
@doc """
@ -38,7 +38,7 @@ defmodule Memex.Accounts do
nil
"""
@spec get_user_by_email_and_password(email :: String.t(), password :: String.t()) ::
@spec get_user_by_email_and_password(String.t(), String.t()) ::
User.t() | nil
def get_user_by_email_and_password(email, password)
when is_binary(email) and is_binary(password) do
@ -86,7 +86,7 @@ defmodule Memex.Accounts do
[%User{}]
"""
@spec list_users_by_role(User.role()) :: [User.t()]
@spec list_users_by_role(:admin | :user) :: [User.t()]
def list_users_by_role(role) do
role = role |> to_string()
Repo.all(from u in User, where: u.role == ^role, order_by: u.email)
@ -106,21 +106,15 @@ defmodule Memex.Accounts do
{:error, %Changeset{}}
"""
@spec register_user(attrs :: map()) :: {:ok, User.t()} | {:error, User.changeset()}
@spec register_user(map()) :: {:ok, User.t()} | {:error, Changeset.t(User.new_user())}
def register_user(attrs) do
Multi.new()
|> Multi.one(:users_count, from(u in User, select: count(u.id), distinct: true))
|> Multi.insert(:add_user, fn %{users_count: count} ->
# if no registered users, make first user an admin
role = if count == 0, do: "admin", else: "user"
role =
if Repo.one!(from u in User, select: count(u.id), distinct: true) == 0,
do: "admin",
else: "user"
User.registration_changeset(attrs) |> User.role_changeset(role)
end)
|> Repo.transaction()
|> case do
{:ok, %{add_user: user}} -> {:ok, user}
{:error, :add_user, changeset, _changes_so_far} -> {:error, changeset}
end
%User{} |> User.registration_changeset(attrs |> Map.put("role", role)) |> Repo.insert()
end
@doc """
@ -132,10 +126,12 @@ defmodule Memex.Accounts do
%Changeset{data: %User{}}
"""
@spec change_user_registration() :: User.changeset()
@spec change_user_registration(attrs :: map()) :: User.changeset()
def change_user_registration(attrs \\ %{}),
do: User.registration_changeset(attrs, hash_password: false)
@spec change_user_registration(User.t() | User.new_user()) ::
Changeset.t(User.t() | User.new_user())
@spec change_user_registration(User.t() | User.new_user(), map()) ::
Changeset.t(User.t() | User.new_user())
def change_user_registration(user, attrs \\ %{}),
do: User.registration_changeset(user, attrs, hash_password: false)
## Settings
@ -148,7 +144,7 @@ defmodule Memex.Accounts do
%Changeset{data: %User{}}
"""
@spec change_user_email(User.t(), attrs :: map()) :: User.changeset()
@spec change_user_email(User.t(), map()) :: Changeset.t(User.t())
def change_user_email(user, attrs \\ %{}), do: User.email_changeset(user, attrs)
@doc """
@ -160,7 +156,7 @@ defmodule Memex.Accounts do
%Changeset{data: %User{}}
"""
@spec change_user_role(User.t(), User.role()) :: User.changeset()
@spec change_user_role(User.t(), atom()) :: Changeset.t(User.t())
def change_user_role(user, role), do: User.role_changeset(user, role)
@doc """
@ -176,8 +172,8 @@ defmodule Memex.Accounts do
{:error, %Changeset{}}
"""
@spec apply_user_email(User.t(), password :: String.t(), attrs :: map()) ::
{:ok, User.t()} | {:error, User.changeset()}
@spec apply_user_email(User.t(), String.t(), map()) ::
{:ok, User.t()} | {:error, Changeset.t(User.t())}
def apply_user_email(user, password, attrs) do
user
|> User.email_changeset(attrs)
@ -191,7 +187,7 @@ defmodule Memex.Accounts do
If the token matches, the user email is updated and the token is deleted.
The confirmed_at date is also updated to the current time.
"""
@spec update_user_email(User.t(), token :: String.t()) :: :ok | :error
@spec update_user_email(User.t(), String.t()) :: :ok | :error
def update_user_email(user, token) do
context = "change:#{user.email}"
@ -204,7 +200,7 @@ defmodule Memex.Accounts do
end
end
@spec user_email_multi(User.t(), email :: String.t(), context :: String.t()) :: Multi.t()
@spec user_email_multi(User.t(), String.t(), String.t()) :: Multi.t()
defp user_email_multi(user, email, context) do
changeset = user |> User.email_changeset(%{email: email}) |> User.confirm_changeset()
@ -222,8 +218,7 @@ defmodule Memex.Accounts do
{:ok, %{to: ..., body: ...}}
"""
@spec deliver_update_email_instructions(User.t(), current_email :: String.t(), function) ::
Job.t()
@spec deliver_update_email_instructions(User.t(), String.t(), function) :: Job.t()
def deliver_update_email_instructions(user, current_email, update_email_url_fun)
when is_function(update_email_url_fun, 1) do
{encoded_token, user_token} = UserToken.build_email_token(user, "change:#{current_email}")
@ -240,7 +235,7 @@ defmodule Memex.Accounts do
%Changeset{data: %User{}}
"""
@spec change_user_password(User.t(), attrs :: map()) :: User.changeset()
@spec change_user_password(User.t(), map()) :: Changeset.t(User.t())
def change_user_password(user, attrs \\ %{}),
do: User.password_changeset(user, attrs, hash_password: false)
@ -256,8 +251,8 @@ defmodule Memex.Accounts do
{:error, %Changeset{}}
"""
@spec update_user_password(User.t(), password :: String.t(), attrs :: map()) ::
{:ok, User.t()} | {:error, User.changeset()}
@spec update_user_password(User.t(), String.t(), map()) ::
{:ok, User.t()} | {:error, Changeset.t(User.t())}
def update_user_password(user, password, attrs) do
changeset =
user
@ -283,7 +278,7 @@ defmodule Memex.Accounts do
%Changeset{data: %User{}}
"""
@spec change_user_locale(User.t()) :: User.changeset()
@spec change_user_locale(User.t()) :: Changeset.t(User.t())
def change_user_locale(%{locale: locale} = user), do: User.locale_changeset(user, locale)
@doc """
@ -299,7 +294,7 @@ defmodule Memex.Accounts do
"""
@spec update_user_locale(User.t(), locale :: String.t()) ::
{:ok, User.t()} | {:error, User.changeset()}
{:ok, User.t()} | {:error, Changeset.t(User.t())}
def update_user_locale(user, locale),
do: user |> User.locale_changeset(locale) |> Repo.update()
@ -315,7 +310,7 @@ defmodule Memex.Accounts do
%User{}
"""
@spec delete_user!(user_to_delete :: User.t(), User.t()) :: User.t()
@spec delete_user!(User.t(), User.t()) :: User.t()
def delete_user!(user, %User{role: :admin}), do: user |> Repo.delete!()
def delete_user!(%User{id: user_id} = user, %User{id: user_id}), do: user |> Repo.delete!()
@ -334,7 +329,7 @@ defmodule Memex.Accounts do
@doc """
Gets the user with the given signed token.
"""
@spec get_user_by_session_token(token :: String.t()) :: User.t()
@spec get_user_by_session_token(String.t()) :: User.t()
def get_user_by_session_token(token) do
{:ok, query} = UserToken.verify_session_token_query(token)
Repo.one(query)
@ -343,7 +338,7 @@ defmodule Memex.Accounts do
@doc """
Deletes the signed token with the given context.
"""
@spec delete_session_token(token :: String.t()) :: :ok
@spec delete_session_token(String.t()) :: :ok
def delete_session_token(token) do
Repo.delete_all(UserToken.token_and_context_query(token, "session"))
:ok
@ -363,16 +358,10 @@ defmodule Memex.Accounts do
"""
@spec is_admin?(User.t()) :: boolean()
def is_admin?(%User{id: user_id}) do
Repo.exists?(from u in User, where: u.id == ^user_id and u.role == :admin)
Repo.one(from u in User, where: u.id == ^user_id and u.role == :admin)
|> is_nil()
end
@doc """
Checks to see if user has the admin role
"""
@spec is_already_admin?(User.t() | nil) :: boolean()
def is_already_admin?(%User{role: :admin}), do: true
def is_already_admin?(_invalid_user), do: false
## Confirmation
@doc """
@ -405,7 +394,7 @@ defmodule Memex.Accounts do
If the token matches, the user account is marked as confirmed
and the token is deleted.
"""
@spec confirm_user(token :: String.t()) :: {:ok, User.t()} | atom()
@spec confirm_user(String.t()) :: {:ok, User.t()} | atom()
def confirm_user(token) do
with {:ok, query} <- UserToken.verify_email_token_query(token, "confirm"),
%User{} = user <- Repo.one(query),
@ -454,7 +443,7 @@ defmodule Memex.Accounts do
nil
"""
@spec get_user_by_reset_password_token(token :: String.t()) :: User.t() | nil
@spec get_user_by_reset_password_token(String.t()) :: User.t() | nil
def get_user_by_reset_password_token(token) do
with {:ok, query} <- UserToken.verify_email_token_query(token, "reset_password"),
%User{} = user <- Repo.one(query) do
@ -476,8 +465,7 @@ defmodule Memex.Accounts do
{:error, %Changeset{}}
"""
@spec reset_user_password(User.t(), attrs :: map()) ::
{:ok, User.t()} | {:error, User.changeset()}
@spec reset_user_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t(User.t())}
def reset_user_password(user, attrs) do
Multi.new()
|> Multi.update(:user, User.password_changeset(user, attrs))

View File

@ -19,8 +19,8 @@ defmodule Memex.Email do
@spec base_email(User.t(), String.t()) :: t()
defp base_email(%User{email: email}, subject) do
from = Application.get_env(:memex, Memex.Mailer)[:email_from] || "noreply@localhost"
name = Application.get_env(:memex, Memex.Mailer)[:email_name]
from = Application.get_env(:Memex, Memex.Mailer)[:email_from] || "noreply@localhost"
name = Application.get_env(:Memex, Memex.Mailer)[:email_name]
new() |> to(email) |> from({name, from}) |> subject(subject)
end

View File

@ -7,7 +7,7 @@ defmodule Memex.Accounts.User do
import Ecto.Changeset
import MemexWeb.Gettext
alias Ecto.{Changeset, UUID}
alias Memex.Invites.Invite
alias Memex.{Accounts.User, Invites.Invite}
@derive {Inspect, except: [:password]}
@primary_key {:id, :binary_id, autogenerate: true}
@ -25,22 +25,20 @@ defmodule Memex.Accounts.User do
timestamps()
end
@type t :: %__MODULE__{
@type t :: %User{
id: id(),
email: String.t(),
password: String.t(),
hashed_password: String.t(),
confirmed_at: NaiveDateTime.t(),
role: role(),
role: atom(),
invites: [Invite.t()],
locale: String.t() | nil,
inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t()
}
@type new_user :: %__MODULE__{}
@type new_user :: %User{}
@type id :: UUID.t()
@type changeset :: Changeset.t(t() | new_user())
@type role :: :user | :admin | String.t()
@doc """
A user changeset for registration.
@ -59,11 +57,12 @@ defmodule Memex.Accounts.User do
validations on a LiveView form), this option can be set to `false`.
Defaults to `true`.
"""
@spec registration_changeset(attrs :: map()) :: changeset()
@spec registration_changeset(attrs :: map(), opts :: keyword()) :: changeset()
def registration_changeset(attrs, opts \\ []) do
%__MODULE__{}
|> cast(attrs, [:email, :password, :locale])
@spec registration_changeset(t() | new_user(), attrs :: map()) :: Changeset.t(t() | new_user())
@spec registration_changeset(t() | new_user(), attrs :: map(), opts :: keyword()) ::
Changeset.t(t() | new_user())
def registration_changeset(user, attrs, opts \\ []) do
user
|> cast(attrs, [:email, :password, :role, :locale])
|> validate_email()
|> validate_password(opts)
end
@ -72,12 +71,12 @@ defmodule Memex.Accounts.User do
A user changeset for role.
"""
@spec role_changeset(t() | new_user() | changeset(), role()) :: changeset()
@spec role_changeset(t(), role :: atom()) :: Changeset.t(t())
def role_changeset(user, role) do
user |> cast(%{"role" => role}, [:role])
end
@spec validate_email(changeset()) :: changeset()
@spec validate_email(Changeset.t(t() | new_user())) :: Changeset.t(t() | new_user())
defp validate_email(changeset) do
changeset
|> validate_required([:email])
@ -89,7 +88,8 @@ defmodule Memex.Accounts.User do
|> unique_constraint(:email)
end
@spec validate_password(changeset(), opts :: keyword()) :: changeset()
@spec validate_password(Changeset.t(t() | new_user()), opts :: keyword()) ::
Changeset.t(t() | new_user())
defp validate_password(changeset, opts) do
changeset
|> validate_required([:password])
@ -100,7 +100,8 @@ defmodule Memex.Accounts.User do
|> maybe_hash_password(opts)
end
@spec maybe_hash_password(changeset(), opts :: keyword()) :: changeset()
@spec maybe_hash_password(Changeset.t(t() | new_user()), opts :: keyword()) ::
Changeset.t(t() | new_user())
defp maybe_hash_password(changeset, opts) do
hash_password? = Keyword.get(opts, :hash_password, true)
password = get_change(changeset, :password)
@ -119,7 +120,7 @@ defmodule Memex.Accounts.User do
It requires the email to change otherwise an error is added.
"""
@spec email_changeset(t(), attrs :: map()) :: changeset()
@spec email_changeset(t(), attrs :: map()) :: Changeset.t(t())
def email_changeset(user, attrs) do
user
|> cast(attrs, [:email])
@ -142,8 +143,8 @@ defmodule Memex.Accounts.User do
validations on a LiveView form), this option can be set to `false`.
Defaults to `true`.
"""
@spec password_changeset(t(), attrs :: map()) :: changeset()
@spec password_changeset(t(), attrs :: map(), opts :: keyword()) :: changeset()
@spec password_changeset(t(), attrs :: map()) :: Changeset.t(t())
@spec password_changeset(t(), attrs :: map(), opts :: keyword()) :: Changeset.t(t())
def password_changeset(user, attrs, opts \\ []) do
user
|> cast(attrs, [:password])
@ -154,7 +155,7 @@ defmodule Memex.Accounts.User do
@doc """
Confirms the account by setting `confirmed_at`.
"""
@spec confirm_changeset(t() | changeset()) :: changeset()
@spec confirm_changeset(t() | Changeset.t(t())) :: Changeset.t(t())
def confirm_changeset(user_or_changeset) do
now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
user_or_changeset |> change(confirmed_at: now)
@ -167,7 +168,7 @@ defmodule Memex.Accounts.User do
`Bcrypt.no_user_verify/0` to avoid timing attacks.
"""
@spec valid_password?(t(), String.t()) :: boolean()
def valid_password?(%__MODULE__{hashed_password: hashed_password}, password)
def valid_password?(%User{hashed_password: hashed_password}, password)
when is_binary(hashed_password) and byte_size(password) > 0 do
Bcrypt.verify_pass(password, hashed_password)
end
@ -180,7 +181,7 @@ defmodule Memex.Accounts.User do
@doc """
Validates the current password otherwise adds an error to the changeset.
"""
@spec validate_current_password(changeset(), String.t()) :: changeset()
@spec validate_current_password(Changeset.t(t()), String.t()) :: Changeset.t(t())
def validate_current_password(changeset, password) do
if valid_password?(changeset.data, password),
do: changeset,
@ -190,7 +191,7 @@ defmodule Memex.Accounts.User do
@doc """
A changeset for changing the user's locale
"""
@spec locale_changeset(t() | changeset(), locale :: String.t() | nil) :: changeset()
@spec locale_changeset(t() | Changeset.t(t()), locale :: String.t() | nil) :: Changeset.t(t())
def locale_changeset(user_or_changeset, locale) do
user_or_changeset
|> cast(%{"locale" => locale}, [:locale])

View File

@ -4,88 +4,21 @@ defmodule Memex.Contexts do
"""
import Ecto.Query, warn: false
alias Memex.{Accounts.User, Contexts.Context, Repo}
alias Memex.Repo
alias Memex.Contexts.Context
@doc """
Returns the list of contexts.
## Examples
iex> list_contexts(%User{id: 123})
iex> list_contexts()
[%Context{}, ...]
iex> list_contexts("my context", %User{id: 123})
[%Context{slug: "my context"}, ...]
"""
@spec list_contexts(User.t()) :: [Context.t()]
@spec list_contexts(search :: String.t() | nil, User.t()) :: [Context.t()]
def list_contexts(search \\ nil, user)
def list_contexts(search, %{id: user_id}) when search |> is_nil() or search == "" do
Repo.all(from c in Context, where: c.user_id == ^user_id, order_by: c.slug)
end
def list_contexts(search, %{id: user_id}) when search |> is_binary() do
trimmed_search = String.trim(search)
Repo.all(
from c in Context,
where: c.user_id == ^user_id,
where:
fragment(
"search @@ websearch_to_tsquery('english', ?)",
^trimmed_search
),
order_by: {
:desc,
fragment(
"ts_rank_cd(search, websearch_to_tsquery('english', ?), 4)",
^trimmed_search
)
}
)
end
@doc """
Returns the list of public contexts for viewing.
## Examples
iex> list_public_contexts()
[%Context{}, ...]
iex> list_public_contexts("my context")
[%Context{slug: "my context"}, ...]
"""
@spec list_public_contexts() :: [Context.t()]
@spec list_public_contexts(search :: String.t() | nil) :: [Context.t()]
def list_public_contexts(search \\ nil)
def list_public_contexts(search) when search |> is_nil() or search == "" do
Repo.all(from c in Context, where: c.visibility == :public, order_by: c.slug)
end
def list_public_contexts(search) when search |> is_binary() do
trimmed_search = String.trim(search)
Repo.all(
from c in Context,
where: c.visibility == :public,
where:
fragment(
"search @@ websearch_to_tsquery('english', ?)",
^trimmed_search
),
order_by: {
:desc,
fragment(
"ts_rank_cd(search, websearch_to_tsquery('english', ?), 4)",
^trimmed_search
)
}
)
def list_contexts do
Repo.all(Context)
end
@doc """
@ -95,78 +28,31 @@ defmodule Memex.Contexts do
## Examples
iex> get_context!(123, %User{id: 123})
iex> get_context!(123)
%Context{}
iex> get_context!(456, %User{id: 123})
iex> get_context!(456)
** (Ecto.NoResultsError)
"""
@spec get_context!(Context.id(), User.t()) :: Context.t()
def get_context!(id, %{id: user_id}) do
Repo.one!(
from c in Context,
where: c.id == ^id,
where: c.user_id == ^user_id or c.visibility in [:public, :unlisted]
)
end
def get_context!(id, _invalid_user) do
Repo.one!(
from c in Context,
where: c.id == ^id,
where: c.visibility in [:public, :unlisted]
)
end
@doc """
Gets a single context by a slug.
Raises `Ecto.NoResultsError` if the Context does not exist.
## Examples
iex> get_context_by_slug("my-context", %User{id: 123})
%Context{}
iex> get_context_by_slug("my-context", %User{id: 123})
** (Ecto.NoResultsError)
"""
@spec get_context_by_slug(Context.slug(), User.t()) :: Context.t() | nil
def get_context_by_slug(slug, %{id: user_id}) do
Repo.one(
from c in Context,
where: c.slug == ^slug,
where: c.user_id == ^user_id or c.visibility in [:public, :unlisted]
)
end
def get_context_by_slug(slug, _invalid_user) do
Repo.one(
from c in Context,
where: c.slug == ^slug,
where: c.visibility in [:public, :unlisted]
)
end
def get_context!(id), do: Repo.get!(Context, id)
@doc """
Creates a context.
## Examples
iex> create_context(%{field: value}, %User{id: 123})
iex> create_context(%{field: value})
{:ok, %Context{}}
iex> create_context(%{field: bad_value}, %User{id: 123})
iex> create_context(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
@spec create_context(User.t()) :: {:ok, Context.t()} | {:error, Context.changeset()}
@spec create_context(attrs :: map(), User.t()) ::
{:ok, Context.t()} | {:error, Context.changeset()}
def create_context(attrs \\ %{}, user) do
Context.create_changeset(attrs, user) |> Repo.insert()
def create_context(attrs \\ %{}) do
%Context{}
|> Context.changeset(attrs)
|> Repo.insert()
end
@doc """
@ -174,18 +60,16 @@ defmodule Memex.Contexts do
## Examples
iex> update_context(context, %{field: new_value}, %User{id: 123})
iex> update_context(context, %{field: new_value})
{:ok, %Context{}}
iex> update_context(context, %{field: bad_value}, %User{id: 123})
iex> update_context(context, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
@spec update_context(Context.t(), attrs :: map(), User.t()) ::
{:ok, Context.t()} | {:error, Context.changeset()}
def update_context(%Context{} = context, attrs, user) do
def update_context(%Context{} = context, attrs) do
context
|> Context.update_changeset(attrs, user)
|> Context.changeset(attrs)
|> Repo.update()
end
@ -194,24 +78,15 @@ defmodule Memex.Contexts do
## Examples
iex> delete_context(%Context{user_id: 123}, %User{id: 123})
iex> delete_context(context)
{:ok, %Context{}}
iex> delete_context(%Context{user_id: 123}, %User{role: :admin})
{:ok, %Context{}}
iex> delete_context(%Context{}, %User{id: 123})
iex> delete_context(context)
{:error, %Ecto.Changeset{}}
"""
@spec delete_context(Context.t(), User.t()) ::
{:ok, Context.t()} | {:error, Context.changeset()}
def delete_context(%Context{user_id: user_id} = context, %{id: user_id}) do
context |> Repo.delete()
end
def delete_context(%Context{} = context, %{role: :admin}) do
context |> Repo.delete()
def delete_context(%Context{} = context) do
Repo.delete(context)
end
@doc """
@ -223,9 +98,7 @@ defmodule Memex.Contexts do
%Ecto.Changeset{data: %Context{}}
"""
@spec change_context(Context.t(), User.t()) :: Context.changeset()
@spec change_context(Context.t(), attrs :: map(), User.t()) :: Context.changeset()
def change_context(%Context{} = context, attrs \\ %{}, user) do
context |> Context.update_changeset(attrs, user)
def change_context(%Context{} = context, attrs \\ %{}) do
Context.changeset(context, attrs)
end
end

View File

@ -1,103 +1,22 @@
defmodule Memex.Contexts.Context do
@moduledoc """
Represents a document that synthesizes multiple concepts as defined by notes
into a single consideration
"""
use Ecto.Schema
import Ecto.Changeset
import MemexWeb.Gettext
alias Ecto.{Changeset, UUID}
alias Memex.{Accounts.User, Repo}
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "contexts" do
field :slug, :string
field :content, :string
field :tags, {:array, :string}
field :tags_string, :string, virtual: true
field :tag, {:array, :string}
field :title, :string
field :visibility, Ecto.Enum, values: [:public, :private, :unlisted]
belongs_to :user, User
timestamps()
end
@type t :: %__MODULE__{
slug: slug(),
content: String.t(),
tags: [String.t()] | nil,
tags_string: String.t() | nil,
visibility: :public | :private | :unlisted,
user: User.t() | Ecto.Association.NotLoaded.t(),
user_id: User.id(),
inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t()
}
@type id :: UUID.t()
@type slug :: String.t()
@type changeset :: Changeset.t(t())
@doc false
@spec create_changeset(attrs :: map(), User.t()) :: changeset()
def create_changeset(attrs, %User{id: user_id}) do
%__MODULE__{}
|> cast(attrs, [:slug, :content, :tags, :visibility])
|> change(user_id: user_id)
|> cast_tags_string(attrs)
|> validate_format(:slug, ~r/^[\p{L}\p{N}\-]+$/,
message: dgettext("errors", "invalid format: only numbers, letters and hyphen are accepted")
)
|> validate_required([:slug, :content, :user_id, :visibility])
|> unique_constraint(:slug)
|> unsafe_validate_unique(:slug, Repo)
def changeset(context, attrs) do
context
|> cast(attrs, [:title, :content, :tag, :visibility])
|> validate_required([:title, :content, :tag, :visibility])
end
@spec update_changeset(t(), attrs :: map(), User.t()) :: changeset()
def update_changeset(%{user_id: user_id} = note, attrs, %User{id: user_id}) do
note
|> cast(attrs, [:slug, :content, :tags, :visibility])
|> cast_tags_string(attrs)
|> validate_format(:slug, ~r/^[\p{L}\p{N}\-]+$/,
message: dgettext("errors", "invalid format: only numbers, letters and hyphen are accepted")
)
|> validate_required([:slug, :content, :visibility])
|> unique_constraint(:slug)
|> unsafe_validate_unique(:slug, Repo)
end
defp cast_tags_string(changeset, attrs) do
changeset
|> put_change(:tags_string, changeset |> get_field(:tags) |> get_tags_string())
|> cast(attrs, [:tags_string])
|> validate_format(:tags_string, ~r/^[\p{L}\p{N}\-\,]+$/,
message:
dgettext(
"errors",
"invalid format: only numbers, letters and hyphen are accepted. tags must be comma-delimited"
)
)
|> cast_tags()
end
defp cast_tags(%{valid?: false} = changeset), do: changeset
defp cast_tags(%{valid?: true} = changeset) do
tags = changeset |> get_field(:tags_string) |> process_tags()
changeset |> put_change(:tags, tags)
end
defp process_tags(tags_string) when tags_string |> is_binary() do
tags_string
|> String.split(",", trim: true)
|> Enum.map(fn str -> str |> String.trim() end)
|> Enum.reject(fn str -> str |> is_nil() end)
|> Enum.sort()
end
defp process_tags(_other_tags_string), do: []
@spec get_tags_string([String.t()] | nil) :: String.t()
def get_tags_string(nil), do: ""
def get_tags_string(tags) when tags |> is_list(), do: tags |> Enum.join(",")
end

View File

@ -0,0 +1,20 @@
defmodule Memex.Contexts.ContextNote do
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "context_notes" do
field :context_id, :binary_id
field :note_id, :binary_id
timestamps()
end
@doc false
def changeset(context_note, attrs) do
context_note
|> cast(attrs, [])
|> validate_required([])
end
end

View File

@ -4,6 +4,7 @@ defmodule Memex.Notes do
"""
import Ecto.Query, warn: false
alias Ecto.Changeset
alias Memex.{Accounts.User, Notes.Note, Repo}
@doc """
@ -14,16 +15,13 @@ defmodule Memex.Notes do
iex> list_notes(%User{id: 123})
[%Note{}, ...]
iex> list_notes("my note", %User{id: 123})
[%Note{slug: "my note"}, ...]
"""
@spec list_notes(User.t()) :: [Note.t()]
@spec list_notes(search :: String.t() | nil, User.t()) :: [Note.t()]
@spec list_notes(User.t() | nil) :: [Note.t()]
@spec list_notes(search :: String.t() | nil, User.t() | nil) :: [Note.t()]
def list_notes(search \\ nil, user)
def list_notes(search, %{id: user_id}) when search |> is_nil() or search == "" do
Repo.all(from n in Note, where: n.user_id == ^user_id, order_by: n.slug)
Repo.all(from n in Note, where: n.user_id == ^user_id, order_by: n.title)
end
def list_notes(search, %{id: user_id}) when search |> is_binary() do
@ -34,13 +32,13 @@ defmodule Memex.Notes do
where: n.user_id == ^user_id,
where:
fragment(
"search @@ websearch_to_tsquery('english', ?)",
"search @@ to_tsquery(websearch_to_tsquery(?)::text || ':*')",
^trimmed_search
),
order_by: {
:desc,
fragment(
"ts_rank_cd(search, websearch_to_tsquery('english', ?), 4)",
"ts_rank_cd(search, to_tsquery(websearch_to_tsquery(?)::text || ':*'), 4)",
^trimmed_search
)
}
@ -54,16 +52,13 @@ defmodule Memex.Notes do
iex> list_public_notes()
[%Note{}, ...]
iex> list_public_notes("my note")
[%Note{slug: "my note"}, ...]
"""
@spec list_public_notes() :: [Note.t()]
@spec list_public_notes(search :: String.t() | nil) :: [Note.t()]
def list_public_notes(search \\ nil)
def list_public_notes(search) when search |> is_nil() or search == "" do
Repo.all(from n in Note, where: n.visibility == :public, order_by: n.slug)
Repo.all(from n in Note, where: n.visibility == :public, order_by: n.title)
end
def list_public_notes(search) when search |> is_binary() do
@ -74,13 +69,13 @@ defmodule Memex.Notes do
where: n.visibility == :public,
where:
fragment(
"search @@ websearch_to_tsquery('english', ?)",
"search @@ to_tsquery(websearch_to_tsquery(?)::text || ':*')",
^trimmed_search
),
order_by: {
:desc,
fragment(
"ts_rank_cd(search, websearch_to_tsquery('english', ?), 4)",
"ts_rank_cd(search, to_tsquery(websearch_to_tsquery(?)::text || ':*'), 4)",
^trimmed_search
)
}
@ -118,37 +113,6 @@ defmodule Memex.Notes do
)
end
@doc """
Gets a single note by slug.
Raises `Ecto.NoResultsError` if the Note does not exist.
## Examples
iex> get_note_by_slug("my-note", %User{id: 123})
%Note{}
iex> get_note_by_slug("my-note", %User{id: 123})
** (Ecto.NoResultsError)
"""
@spec get_note_by_slug(Note.slug(), User.t()) :: Note.t() | nil
def get_note_by_slug(slug, %{id: user_id}) do
Repo.one(
from n in Note,
where: n.slug == ^slug,
where: n.user_id == ^user_id or n.visibility in [:public, :unlisted]
)
end
def get_note_by_slug(slug, _invalid_user) do
Repo.one(
from n in Note,
where: n.slug == ^slug,
where: n.visibility in [:public, :unlisted]
)
end
@doc """
Creates a note.
@ -161,8 +125,8 @@ defmodule Memex.Notes do
{:error, %Ecto.Changeset{}}
"""
@spec create_note(User.t()) :: {:ok, Note.t()} | {:error, Note.changeset()}
@spec create_note(attrs :: map(), User.t()) :: {:ok, Note.t()} | {:error, Note.changeset()}
@spec create_note(User.t()) :: {:ok, Note.t()} | {:error, Changeset.t()}
@spec create_note(attrs :: map(), User.t()) :: {:ok, Note.t()} | {:error, Changeset.t()}
def create_note(attrs \\ %{}, user) do
Note.create_changeset(attrs, user) |> Repo.insert()
end
@ -180,7 +144,7 @@ defmodule Memex.Notes do
"""
@spec update_note(Note.t(), attrs :: map(), User.t()) ::
{:ok, Note.t()} | {:error, Note.changeset()}
{:ok, Note.t()} | {:error, Changeset.t()}
def update_note(%Note{} = note, attrs, user) do
note
|> Note.update_changeset(attrs, user)
@ -192,25 +156,18 @@ defmodule Memex.Notes do
## Examples
iex> delete_note(%Note{user_id: 123}, %User{id: 123})
iex> delete_note(note, %User{id: 123})
{:ok, %Note{}}
iex> delete_note(%Note{}, %User{role: :admin})
{:ok, %Note{}}
iex> delete_note(%Note{}, %User{id: 123})
iex> delete_note(note, %User{id: 123})
{:error, %Ecto.Changeset{}}
"""
@spec delete_note(Note.t(), User.t()) :: {:ok, Note.t()} | {:error, Note.changeset()}
@spec delete_note(Note.t(), User.t()) :: {:ok, Note.t()} | {:error, Changeset.t()}
def delete_note(%Note{user_id: user_id} = note, %{id: user_id}) do
note |> Repo.delete()
end
def delete_note(%Note{} = note, %{role: :admin}) do
note |> Repo.delete()
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking note changes.
@ -219,13 +176,27 @@ defmodule Memex.Notes do
iex> change_note(note, %User{id: 123})
%Ecto.Changeset{data: %Note{}}
iex> change_note(note, %{slug: "new slug"}, %User{id: 123})
iex> change_note(note, %{title: "new title"}, %User{id: 123})
%Ecto.Changeset{data: %Note{}}
"""
@spec change_note(Note.t(), User.t()) :: Note.changeset()
@spec change_note(Note.t(), attrs :: map(), User.t()) :: Note.changeset()
@spec change_note(Note.t(), User.t()) :: Changeset.t(Note.t())
@spec change_note(Note.t(), attrs :: map(), User.t()) :: Changeset.t(Note.t())
def change_note(%Note{} = note, attrs \\ %{}, user) do
note |> Note.update_changeset(attrs, user)
end
@doc """
Gets a canonical string representation of the `:tags` field for a Note
"""
@spec get_tags_string(Note.t() | Changeset.t() | [String.t()] | nil) :: String.t()
def get_tags_string(nil), do: ""
def get_tags_string(tags) when tags |> is_list(), do: tags |> Enum.join(",")
def get_tags_string(%Note{tags: tags}), do: tags |> get_tags_string()
def get_tags_string(%Changeset{} = changeset) do
changeset
|> Changeset.get_field(:tags)
|> get_tags_string()
end
end

View File

@ -4,17 +4,16 @@ defmodule Memex.Notes.Note do
"""
use Ecto.Schema
import Ecto.Changeset
import MemexWeb.Gettext
alias Ecto.{Changeset, UUID}
alias Memex.{Accounts.User, Repo}
alias Ecto.UUID
alias Memex.{Accounts.User, Notes.Note}
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "notes" do
field :slug, :string
field :content, :string
field :tags, {:array, :string}
field :tags_string, :string, virtual: true
field :title, :string
field :visibility, Ecto.Enum, values: [:public, :private, :unlisted]
belongs_to :user, User
@ -22,81 +21,34 @@ defmodule Memex.Notes.Note do
timestamps()
end
@type t :: %__MODULE__{
slug: slug(),
content: String.t(),
tags: [String.t()] | nil,
tags_string: String.t() | nil,
visibility: :public | :private | :unlisted,
user: User.t() | Ecto.Association.NotLoaded.t(),
user_id: User.id(),
inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t()
}
@type t :: %Note{}
@type id :: UUID.t()
@type slug :: String.t()
@type changeset :: Changeset.t(t())
@doc false
@spec create_changeset(attrs :: map(), User.t()) :: changeset()
def create_changeset(attrs, %User{id: user_id}) do
%__MODULE__{}
|> cast(attrs, [:slug, :content, :tags, :visibility])
%Note{}
|> cast(attrs, [:title, :content, :tags, :visibility])
|> change(user_id: user_id)
|> cast_tags_string(attrs)
|> validate_format(:slug, ~r/^[\p{L}\p{N}\-]+$/,
message: dgettext("errors", "invalid format: only numbers, letters and hyphen are accepted")
)
|> validate_required([:slug, :content, :user_id, :visibility])
|> unique_constraint(:slug)
|> unsafe_validate_unique(:slug, Repo)
|> validate_required([:title, :content, :user_id, :visibility])
end
@spec update_changeset(t(), attrs :: map(), User.t()) :: changeset()
def update_changeset(%{user_id: user_id} = note, attrs, %User{id: user_id}) do
note
|> cast(attrs, [:slug, :content, :tags, :visibility])
|> cast(attrs, [:title, :content, :tags, :visibility])
|> cast_tags_string(attrs)
|> validate_format(:slug, ~r/^[\p{L}\p{N}\-]+$/,
message: dgettext("errors", "invalid format: only numbers, letters and hyphen are accepted")
)
|> validate_required([:slug, :content, :visibility])
|> unique_constraint(:slug)
|> unsafe_validate_unique(:slug, Repo)
|> validate_required([:title, :content, :visibility])
end
defp cast_tags_string(changeset, attrs) do
changeset
|> put_change(:tags_string, changeset |> get_field(:tags) |> get_tags_string())
|> cast(attrs, [:tags_string])
|> validate_format(:tags_string, ~r/^[\p{L}\p{N}\-\,]+$/,
message:
dgettext(
"errors",
"invalid format: only numbers, letters and hyphen are accepted. tags must be comma-delimited"
)
)
|> cast_tags()
end
defp cast_tags(%{valid?: false} = changeset), do: changeset
defp cast_tags(%{valid?: true} = changeset) do
tags = changeset |> get_field(:tags_string) |> process_tags()
changeset |> put_change(:tags, tags)
end
defp process_tags(tags_string) when tags_string |> is_binary() do
defp cast_tags_string(changeset, %{"tags_string" => tags_string}) when is_binary(tags_string) do
tags =
tags_string
|> String.split(",", trim: true)
|> Enum.map(fn str -> str |> String.trim() end)
|> Enum.reject(fn str -> str |> is_nil() end)
|> Enum.sort()
changeset |> change(tags: tags)
end
defp process_tags(_other_tags_string), do: []
@spec get_tags_string([String.t()] | nil) :: String.t()
def get_tags_string(nil), do: ""
def get_tags_string(tags) when tags |> is_list(), do: tags |> Enum.join(",")
defp cast_tags_string(changeset, _attrs), do: changeset
end

View File

@ -4,87 +4,21 @@ defmodule Memex.Pipelines do
"""
import Ecto.Query, warn: false
alias Memex.{Accounts.User, Pipelines.Pipeline, Repo}
alias Memex.Repo
alias Memex.Pipelines.Pipeline
@doc """
Returns the list of pipelines.
## Examples
iex> list_pipelines(%User{id: 123})
iex> list_pipelines()
[%Pipeline{}, ...]
iex> list_pipelines("my pipeline", %User{id: 123})
[%Pipeline{slug: "my pipeline"}, ...]
"""
@spec list_pipelines(User.t()) :: [Pipeline.t()]
@spec list_pipelines(search :: String.t() | nil, User.t()) :: [Pipeline.t()]
def list_pipelines(search \\ nil, user)
def list_pipelines(search, %{id: user_id}) when search |> is_nil() or search == "" do
Repo.all(from p in Pipeline, where: p.user_id == ^user_id, order_by: p.slug)
end
def list_pipelines(search, %{id: user_id}) when search |> is_binary() do
trimmed_search = String.trim(search)
Repo.all(
from p in Pipeline,
where: p.user_id == ^user_id,
where:
fragment(
"search @@ websearch_to_tsquery('english', ?)",
^trimmed_search
),
order_by: {
:desc,
fragment(
"ts_rank_cd(search, websearch_to_tsquery('english', ?), 4)",
^trimmed_search
)
}
)
end
@doc """
Returns the list of public pipelines for viewing
## Examples
iex> list_public_pipelines()
[%Pipeline{}, ...]
iex> list_public_pipelines("my pipeline")
[%Pipeline{slug: "my pipeline"}, ...]
"""
@spec list_public_pipelines() :: [Pipeline.t()]
@spec list_public_pipelines(search :: String.t() | nil) :: [Pipeline.t()]
def list_public_pipelines(search \\ nil)
def list_public_pipelines(search) when search |> is_nil() or search == "" do
Repo.all(from p in Pipeline, where: p.visibility == :public, order_by: p.slug)
end
def list_public_pipelines(search) when search |> is_binary() do
trimmed_search = String.trim(search)
Repo.all(
from p in Pipeline,
where: p.visibility == :public,
where:
fragment(
"search @@ websearch_to_tsquery('english', ?)",
^trimmed_search
),
order_by: {
:desc,
fragment(
"ts_rank_cd(search, websearch_to_tsquery('english', ?), 4)",
^trimmed_search
)
}
)
def list_pipelines do
Repo.all(Pipeline)
end
@doc """
@ -94,78 +28,31 @@ defmodule Memex.Pipelines do
## Examples
iex> get_pipeline!(123, %User{id: 123})
iex> get_pipeline!(123)
%Pipeline{}
iex> get_pipeline!(456, %User{id: 123})
iex> get_pipeline!(456)
** (Ecto.NoResultsError)
"""
@spec get_pipeline!(Pipeline.id(), User.t()) :: Pipeline.t()
def get_pipeline!(id, %{id: user_id}) do
Repo.one!(
from p in Pipeline,
where: p.id == ^id,
where: p.user_id == ^user_id or p.visibility in [:public, :unlisted]
)
end
def get_pipeline!(id, _invalid_user) do
Repo.one!(
from p in Pipeline,
where: p.id == ^id,
where: p.visibility in [:public, :unlisted]
)
end
@doc """
Gets a single pipeline by it's slug.
Raises `Ecto.NoResultsError` if the Pipeline does not exist.
## Examples
iex> get_pipeline_by_slug("my-pipeline", %User{id: 123})
%Pipeline{}
iex> get_pipeline_by_slug("my-pipeline", %User{id: 123})
** (Ecto.NoResultsError)
"""
@spec get_pipeline_by_slug(Pipeline.slug(), User.t()) :: Pipeline.t() | nil
def get_pipeline_by_slug(slug, %{id: user_id}) do
Repo.one(
from p in Pipeline,
where: p.slug == ^slug,
where: p.user_id == ^user_id or p.visibility in [:public, :unlisted]
)
end
def get_pipeline_by_slug(slug, _invalid_user) do
Repo.one(
from p in Pipeline,
where: p.slug == ^slug,
where: p.visibility in [:public, :unlisted]
)
end
def get_pipeline!(id), do: Repo.get!(Pipeline, id)
@doc """
Creates a pipeline.
## Examples
iex> create_pipeline(%{field: value}, %User{id: 123})
iex> create_pipeline(%{field: value})
{:ok, %Pipeline{}}
iex> create_pipeline(%{field: bad_value}, %User{id: 123})
iex> create_pipeline(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
@spec create_pipeline(User.t()) :: {:ok, Pipeline.t()} | {:error, Pipeline.changeset()}
@spec create_pipeline(attrs :: map(), User.t()) ::
{:ok, Pipeline.t()} | {:error, Pipeline.changeset()}
def create_pipeline(attrs \\ %{}, user) do
Pipeline.create_changeset(attrs, user) |> Repo.insert()
def create_pipeline(attrs \\ %{}) do
%Pipeline{}
|> Pipeline.changeset(attrs)
|> Repo.insert()
end
@doc """
@ -173,18 +60,16 @@ defmodule Memex.Pipelines do
## Examples
iex> update_pipeline(pipeline, %{field: new_value}, %User{id: 123})
iex> update_pipeline(pipeline, %{field: new_value})
{:ok, %Pipeline{}}
iex> update_pipeline(pipeline, %{field: bad_value}, %User{id: 123})
iex> update_pipeline(pipeline, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
@spec update_pipeline(Pipeline.t(), attrs :: map(), User.t()) ::
{:ok, Pipeline.t()} | {:error, Pipeline.changeset()}
def update_pipeline(%Pipeline{} = pipeline, attrs, user) do
def update_pipeline(%Pipeline{} = pipeline, attrs) do
pipeline
|> Pipeline.update_changeset(attrs, user)
|> Pipeline.changeset(attrs)
|> Repo.update()
end
@ -193,24 +78,15 @@ defmodule Memex.Pipelines do
## Examples
iex> delete_pipeline(%Pipeline{user_id: 123}, %User{id: 123})
iex> delete_pipeline(pipeline)
{:ok, %Pipeline{}}
iex> delete_pipeline(%Pipeline{}, %User{role: :admin})
{:ok, %Pipeline{}}
iex> delete_pipeline(%Pipeline{}, %User{id: 123})
iex> delete_pipeline(pipeline)
{:error, %Ecto.Changeset{}}
"""
@spec delete_pipeline(Pipeline.t(), User.t()) ::
{:ok, Pipeline.t()} | {:error, Pipeline.changeset()}
def delete_pipeline(%Pipeline{user_id: user_id} = pipeline, %{id: user_id}) do
pipeline |> Repo.delete()
end
def delete_pipeline(%Pipeline{} = pipeline, %{role: :admin}) do
pipeline |> Repo.delete()
def delete_pipeline(%Pipeline{} = pipeline) do
Repo.delete(pipeline)
end
@doc """
@ -218,16 +94,11 @@ defmodule Memex.Pipelines do
## Examples
iex> change_pipeline(pipeline, %User{id: 123})
%Ecto.Changeset{data: %Pipeline{}}
iex> change_pipeline(pipeline, %{slug: "new slug"}, %User{id: 123})
iex> change_pipeline(pipeline)
%Ecto.Changeset{data: %Pipeline{}}
"""
@spec change_pipeline(Pipeline.t(), User.t()) :: Pipeline.changeset()
@spec change_pipeline(Pipeline.t(), attrs :: map(), User.t()) :: Pipeline.changeset()
def change_pipeline(%Pipeline{} = pipeline, attrs \\ %{}, user) do
pipeline |> Pipeline.update_changeset(attrs, user)
def change_pipeline(%Pipeline{} = pipeline, attrs \\ %{}) do
Pipeline.changeset(pipeline, attrs)
end
end

View File

@ -1,104 +1,21 @@
defmodule Memex.Pipelines.Pipeline do
@moduledoc """
Represents a chain of considerations to take to accomplish a task
"""
use Ecto.Schema
import Ecto.Changeset
import MemexWeb.Gettext
alias Ecto.{Changeset, UUID}
alias Memex.{Accounts.User, Pipelines.Steps.Step, Repo}
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "pipelines" do
field :slug, :string
field :description, :string
field :tags, {:array, :string}
field :tags_string, :string, virtual: true
field :title, :string
field :visibility, Ecto.Enum, values: [:public, :private, :unlisted]
belongs_to :user, User
has_many :steps, Step, preload_order: [asc: :position]
timestamps()
end
@type t :: %__MODULE__{
slug: slug(),
description: String.t(),
tags: [String.t()] | nil,
tags_string: String.t() | nil,
visibility: :public | :private | :unlisted,
user: User.t() | Ecto.Association.NotLoaded.t(),
user_id: User.id(),
inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t()
}
@type id :: UUID.t()
@type slug :: String.t()
@type changeset :: Changeset.t(t())
@doc false
@spec create_changeset(attrs :: map(), User.t()) :: changeset()
def create_changeset(attrs, %User{id: user_id}) do
%__MODULE__{}
|> cast(attrs, [:slug, :description, :tags, :visibility])
|> change(user_id: user_id)
|> cast_tags_string(attrs)
|> validate_format(:slug, ~r/^[\p{L}\p{N}\-]+$/,
message: dgettext("errors", "invalid format: only numbers, letters and hyphen are accepted")
)
|> validate_required([:slug, :user_id, :visibility])
|> unique_constraint(:slug)
|> unsafe_validate_unique(:slug, Repo)
end
@spec update_changeset(t(), attrs :: map(), User.t()) :: changeset()
def update_changeset(%{user_id: user_id} = pipeline, attrs, %User{id: user_id}) do
def changeset(pipeline, attrs) do
pipeline
|> cast(attrs, [:slug, :description, :tags, :visibility])
|> cast_tags_string(attrs)
|> validate_format(:slug, ~r/^[\p{L}\p{N}\-]+$/,
message: dgettext("errors", "invalid format: only numbers, letters and hyphen are accepted")
)
|> validate_required([:slug, :visibility])
|> unique_constraint(:slug)
|> unsafe_validate_unique(:slug, Repo)
|> cast(attrs, [:title, :description, :visibility])
|> validate_required([:title, :description, :visibility])
end
defp cast_tags_string(changeset, attrs) do
changeset
|> put_change(:tags_string, changeset |> get_field(:tags) |> get_tags_string())
|> cast(attrs, [:tags_string])
|> validate_format(:tags_string, ~r/^[\p{L}\p{N}\-\,]+$/,
message:
dgettext(
"errors",
"invalid format: only numbers, letters and hyphen are accepted. tags must be comma-delimited"
)
)
|> cast_tags()
end
defp cast_tags(%{valid?: false} = changeset), do: changeset
defp cast_tags(%{valid?: true} = changeset) do
tags = changeset |> get_field(:tags_string) |> process_tags()
changeset |> put_change(:tags, tags)
end
defp process_tags(tags_string) when tags_string |> is_binary() do
tags_string
|> String.split(",", trim: true)
|> Enum.map(fn str -> str |> String.trim() end)
|> Enum.reject(fn str -> str |> is_nil() end)
|> Enum.sort()
end
defp process_tags(_other_tags_string), do: []
@spec get_tags_string([String.t()] | nil) :: String.t()
def get_tags_string(nil), do: ""
def get_tags_string(tags) when tags |> is_list(), do: tags |> Enum.join(",")
end

View File

@ -1,71 +0,0 @@
defmodule Memex.Pipelines.Steps.Step do
@moduledoc """
Represents a step taken while executing a pipeline
"""
use Ecto.Schema
import Ecto.Changeset
alias Ecto.{Changeset, UUID}
alias Memex.{Accounts.User, Pipelines.Pipeline}
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "steps" do
field :title, :string
field :content, :string
field :position, :integer
belongs_to :pipeline, Pipeline
belongs_to :user, User
timestamps()
end
@type t :: %__MODULE__{
title: String.t(),
content: String.t(),
position: non_neg_integer(),
pipeline: Pipeline.t() | Ecto.Association.NotLoaded.t(),
pipeline_id: Pipeline.id(),
user: User.t() | Ecto.Association.NotLoaded.t(),
user_id: User.id(),
inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t()
}
@type id :: UUID.t()
@type changeset :: Changeset.t(t())
@doc false
@spec create_changeset(attrs :: map(), position :: non_neg_integer(), Pipeline.t(), User.t()) ::
changeset()
def create_changeset(attrs, position, %Pipeline{id: pipeline_id, user_id: user_id}, %User{
id: user_id
}) do
%__MODULE__{}
|> cast(attrs, [:title, :content])
|> change(pipeline_id: pipeline_id, user_id: user_id, position: position)
|> validate_required([:title, :content, :user_id, :position])
end
@spec update_changeset(t(), attrs :: map(), User.t()) ::
changeset()
def update_changeset(
%{user_id: user_id} = step,
attrs,
%User{id: user_id}
) do
step
|> cast(attrs, [:title, :content])
|> validate_required([:title, :content, :user_id, :position])
end
@spec position_changeset(t(), position :: non_neg_integer(), User.t()) :: changeset()
def position_changeset(
%{user_id: user_id} = step,
position,
%User{id: user_id}
) do
step
|> change(position: position)
|> validate_required([:title, :content, :user_id, :position])
end
end

View File

@ -1,238 +0,0 @@
defmodule Memex.Pipelines.Steps do
@moduledoc """
The context for steps within a pipeline
"""
import Ecto.Query, warn: false
alias Ecto.Multi
alias Memex.{Accounts.User, Repo}
alias Memex.Pipelines.{Pipeline, Steps.Step}
@doc """
Returns the list of steps.
## Examples
iex> list_steps(%User{id: 123})
[%Step{}, ...]
iex> list_steps("my step", %User{id: 123})
[%Step{title: "my step"}, ...]
"""
@spec list_steps(Pipeline.t(), User.t()) :: [Step.t()]
def list_steps(%{id: pipeline_id}, %{id: user_id}) do
Repo.all(
from s in Step,
where: s.pipeline_id == ^pipeline_id,
where: s.user_id == ^user_id,
order_by: s.position
)
end
def list_steps(%{id: pipeline_id, visibility: visibility}, _invalid_user)
when visibility in [:unlisted, :public] do
Repo.all(
from s in Step,
where: s.pipeline_id == ^pipeline_id,
order_by: s.position
)
end
@doc """
Preloads the `:steps` field on a Memex.Pipelines.Pipeline
"""
@spec preload_steps(Pipeline.t(), User.t()) :: Pipeline.t()
def preload_steps(pipeline, user) do
%{pipeline | steps: list_steps(pipeline, user)}
end
@doc """
Gets a single step.
Raises `Ecto.NoResultsError` if the Step does not exist.
## Examples
iex> get_step!(123, %User{id: 123})
%Step{}
iex> get_step!(456, %User{id: 123})
** (Ecto.NoResultsError)
"""
@spec get_step!(Step.id(), User.t()) :: Step.t()
def get_step!(id, %{id: user_id}) do
Repo.one!(from n in Step, where: n.id == ^id, where: n.user_id == ^user_id)
end
def get_step!(id, _invalid_user) do
Repo.one!(
from n in Step,
where: n.id == ^id,
where: n.visibility in [:public, :unlisted]
)
end
@doc """
Creates a step.
## Examples
iex> create_step(%{field: value}, %User{id: 123})
{:ok, %Step{}}
iex> create_step(%{field: bad_value}, %User{id: 123})
{:error, %Ecto.Changeset{}}
"""
@spec create_step(position :: non_neg_integer(), Pipeline.t(), User.t()) ::
{:ok, Step.t()} | {:error, Step.changeset()}
@spec create_step(attrs :: map(), position :: non_neg_integer(), Pipeline.t(), User.t()) ::
{:ok, Step.t()} | {:error, Step.changeset()}
def create_step(attrs \\ %{}, position, pipeline, user) do
Step.create_changeset(attrs, position, pipeline, user) |> Repo.insert()
end
@doc """
Updates a step.
## Examples
iex> update_step(step, %{field: new_value}, %User{id: 123})
{:ok, %Step{}}
iex> update_step(step, %{field: bad_value}, %User{id: 123})
{:error, %Ecto.Changeset{}}
"""
@spec update_step(Step.t(), attrs :: map(), User.t()) ::
{:ok, Step.t()} | {:error, Step.changeset()}
def update_step(%Step{} = step, attrs, user) do
step
|> Step.update_changeset(attrs, user)
|> Repo.update()
end
@doc """
Deletes a step.
## Examples
iex> delete_step(%Step{user_id: 123}, %User{id: 123})
{:ok, %Step{}}
iex> delete_step(%Step{}, %User{role: :admin})
{:ok, %Step{}}
iex> delete_step(%Step{}, %User{id: 123})
{:error, %Ecto.Changeset{}}
"""
@spec delete_step(Step.t(), User.t()) :: {:ok, Step.t()} | {:error, Step.changeset()}
def delete_step(%Step{user_id: user_id} = step, %{id: user_id}) do
delete_step(step)
end
def delete_step(%Step{} = step, %{role: :admin}) do
delete_step(step)
end
defp delete_step(step) do
Multi.new()
|> Multi.delete(:delete_step, step)
|> Multi.update_all(
:reorder_steps,
fn %{delete_step: %{position: position, pipeline_id: pipeline_id}} ->
from s in Step,
where: s.pipeline_id == ^pipeline_id,
where: s.position > ^position,
update: [set: [position: s.position - 1]]
end,
[]
)
|> Repo.transaction()
|> case do
{:ok, %{delete_step: step}} -> {:ok, step}
{:error, :delete_step, changeset, _changes_so_far} -> {:error, changeset}
end
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking step changes.
## Examples
iex> change_step(step, %User{id: 123})
%Ecto.Changeset{data: %Step{}}
iex> change_step(step, %{title: "new title"}, %User{id: 123})
%Ecto.Changeset{data: %Step{}}
"""
@spec change_step(Step.t(), User.t()) :: Step.changeset()
@spec change_step(Step.t(), attrs :: map(), User.t()) :: Step.changeset()
def change_step(%Step{} = step, attrs \\ %{}, user) do
step |> Step.update_changeset(attrs, user)
end
@spec reorder_step(Step.t(), :up | :down, User.t()) ::
{:ok, Step.t()} | {:error, Step.changeset()}
def reorder_step(%Step{position: 0} = step, :up, _user), do: {:error, step}
def reorder_step(
%Step{position: position, pipeline_id: pipeline_id, user_id: user_id} = step,
:up,
%{id: user_id} = user
) do
Multi.new()
|> Multi.update_all(
:reorder_steps,
from(s in Step,
where: s.pipeline_id == ^pipeline_id,
where: s.position == ^position - 1,
update: [set: [position: ^position]]
),
[]
)
|> Multi.update(
:update_step,
step |> Step.position_changeset(position - 1, user)
)
|> Repo.transaction()
|> case do
{:ok, %{update_step: step}} -> {:ok, step}
{:error, :update_step, changeset, _changes_so_far} -> {:error, changeset}
end
end
def reorder_step(
%Step{pipeline_id: pipeline_id, position: position, user_id: user_id} = step,
:down,
%{id: user_id} = user
) do
Multi.new()
|> Multi.one(
:step_count,
from(s in Step, where: s.pipeline_id == ^pipeline_id, distinct: true, select: count(s.id))
)
|> Multi.update_all(
:reorder_steps,
from(s in Step,
where: s.pipeline_id == ^pipeline_id,
where: s.position == ^position + 1,
update: [set: [position: ^position]]
),
[]
)
|> Multi.update(:update_step, fn %{step_count: step_count} ->
new_position = if position >= step_count - 1, do: position, else: position + 1
step |> Step.position_changeset(new_position, user)
end)
|> Repo.transaction()
|> case do
{:ok, %{update_step: step}} -> {:ok, step}
{:error, :update_step, changeset, _changes_so_far} -> {:error, changeset}
end
end
end

104
lib/memex/steps.ex Normal file
View File

@ -0,0 +1,104 @@
defmodule Memex.Steps do
@moduledoc """
The Steps context.
"""
import Ecto.Query, warn: false
alias Memex.Repo
alias Memex.Steps.Step
@doc """
Returns the list of steps.
## Examples
iex> list_steps()
[%Step{}, ...]
"""
def list_steps do
Repo.all(Step)
end
@doc """
Gets a single step.
Raises `Ecto.NoResultsError` if the Step does not exist.
## Examples
iex> get_step!(123)
%Step{}
iex> get_step!(456)
** (Ecto.NoResultsError)
"""
def get_step!(id), do: Repo.get!(Step, id)
@doc """
Creates a step.
## Examples
iex> create_step(%{field: value})
{:ok, %Step{}}
iex> create_step(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_step(attrs \\ %{}) do
%Step{}
|> Step.changeset(attrs)
|> Repo.insert()
end
@doc """
Updates a step.
## Examples
iex> update_step(step, %{field: new_value})
{:ok, %Step{}}
iex> update_step(step, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_step(%Step{} = step, attrs) do
step
|> Step.changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a step.
## Examples
iex> delete_step(step)
{:ok, %Step{}}
iex> delete_step(step)
{:error, %Ecto.Changeset{}}
"""
def delete_step(%Step{} = step) do
Repo.delete(step)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking step changes.
## Examples
iex> change_step(step)
%Ecto.Changeset{data: %Step{}}
"""
def change_step(%Step{} = step, attrs \\ %{}) do
Step.changeset(step, attrs)
end
end

22
lib/memex/steps/step.ex Normal file
View File

@ -0,0 +1,22 @@
defmodule Memex.Steps.Step do
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "steps" do
field :description, :string
field :position, :integer
field :title, :string
field :pipeline_id, :binary_id
timestamps()
end
@doc false
def changeset(step, attrs) do
step
|> cast(attrs, [:title, :description, :position])
|> validate_required([:title, :description, :position])
end
end

View File

@ -0,0 +1,20 @@
defmodule Memex.Steps.StepContext do
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "step_contexts" do
field :step_id, :binary_id
field :context_id, :binary_id
timestamps()
end
@doc false
def changeset(step_context, attrs) do
step_context
|> cast(attrs, [])
|> validate_required([])
end
end

View File

@ -1,45 +0,0 @@
defmodule MemexWeb.Components.ContextContent do
@moduledoc """
Display the content for a context
"""
use MemexWeb, :component
alias Memex.Contexts.Context
alias Phoenix.HTML
attr :context, Context, required: true
def context_content(assigns) do
~H"""
<div
id={"show-context-content-#{@context.id}"}
class="input input-primary h-128 min-h-128 inline-block overflow-x-hidden overflow-y-auto"
phx-hook="MaintainAttrs"
phx-update="ignore"
readonly
phx-no-format
><p class="inline"><%= add_links_to_content(@context.content) %></p></div>
"""
end
defp add_links_to_content(content) do
Regex.replace(
~r/\[\[([\p{L}\p{N}\-]+)\]\]/,
content,
fn _whole_match, slug ->
link =
HTML.Link.link(
"[[#{slug}]]",
to: Routes.note_show_path(Endpoint, :show, slug),
class: "link inline",
data: [qa: "context-note-#{slug}"]
)
|> HTML.Safe.to_iodata()
|> IO.iodata_to_binary()
"</p>#{link}<p class=\"inline\">"
end
)
|> String.replace("\n", "<br>")
|> HTML.raw()
end
end

View File

@ -1,132 +0,0 @@
defmodule MemexWeb.Components.ContextsTableComponent do
@moduledoc """
A component that displays a list of contexts
"""
use MemexWeb, :live_component
alias Ecto.UUID
alias Memex.{Accounts.User, Contexts.Context}
alias Phoenix.LiveView.{Rendered, Socket}
@impl true
@spec update(
%{
required(:id) => UUID.t(),
required(:current_user) => User.t(),
required(:contexts) => [Context.t()],
optional(any()) => any()
},
Socket.t()
) :: {:ok, Socket.t()}
def update(%{id: _id, contexts: _contexts, current_user: _current_user} = assigns, socket) do
socket =
socket
|> assign(assigns)
|> assign_new(:actions, fn -> [] end)
|> display_contexts()
{:ok, socket}
end
defp display_contexts(
%{
assigns: %{
contexts: contexts,
current_user: current_user,
actions: actions
}
} = socket
) do
columns =
if actions == [] or current_user |> is_nil() do
[]
else
[%{label: nil, key: :actions, sortable: false}]
end
columns = [
%{label: gettext("slug"), key: :slug},
%{label: gettext("tags"), key: :tags},
%{label: gettext("visibility"), key: :visibility}
| columns
]
rows =
contexts
|> Enum.map(fn context ->
context
|> get_row_data_for_context(%{
columns: columns,
current_user: current_user,
actions: actions
})
end)
socket |> assign(columns: columns, rows: rows)
end
@impl true
def render(assigns) do
~H"""
<div class="w-full">
<.live_component
module={MemexWeb.Components.TableComponent}
id={@id}
columns={@columns}
rows={@rows}
/>
</div>
"""
end
@spec get_row_data_for_context(Context.t(), additional_data :: map()) :: map()
defp get_row_data_for_context(context, %{columns: columns} = additional_data) do
columns
|> Map.new(fn %{key: key} ->
{key, get_value_for_key(key, context, additional_data)}
end)
end
@spec get_value_for_key(atom(), Context.t(), additional_data :: map()) ::
any() | {any(), Rendered.t()}
defp get_value_for_key(:slug, %{slug: slug}, _additional_data) do
assigns = %{slug: slug}
slug_block = ~H"""
<.link
navigate={Routes.context_show_path(Endpoint, :show, @slug)}
class="link"
data-qa={"context-show-#{@slug}"}
>
<%= @slug %>
</.link>
"""
{slug, slug_block}
end
defp get_value_for_key(:tags, %{tags: tags}, _additional_data) do
assigns = %{tags: tags}
~H"""
<div class="flex flex-wrap justify-center space-x-1">
<%= for tag <- @tags do %>
<.link patch={Routes.context_index_path(Endpoint, :search, tag)} class="link">
<%= tag %>
</.link>
<% end %>
</div>
"""
end
defp get_value_for_key(:actions, context, %{actions: actions}) do
assigns = %{actions: actions, context: context}
~H"""
<div class="flex justify-center items-center space-x-4">
<%= render_slot(@actions, @context) %>
</div>
"""
end
defp get_value_for_key(key, context, _additional_data), do: context |> Map.get(key)
end

View File

@ -0,0 +1,29 @@
defmodule MemexWeb.Components.NoteCard do
@moduledoc """
Display card for an note
"""
use MemexWeb, :component
def note_card(assigns) do
~H"""
<div class="mx-4 my-2 px-8 py-4 flex flex-col justify-center items-center space-y-4
border border-gray-400 rounded-lg shadow-lg hover:shadow-md
transition-all duration-300 ease-in-out">
<h1 class="title text-xl">
<%= @note.name %>
</h1>
<h2 class="title text-md">
<%= gettext("visibility: %{visibility}", visibility: @note.visibility) %>
</h2>
<%= if @inner_block do %>
<div class="flex space-x-4 justify-center items-center">
<%= render_slot(@inner_block) %>
</div>
<% end %>
</div>
"""
end
end

View File

@ -1,45 +0,0 @@
defmodule MemexWeb.Components.NoteContent do
@moduledoc """
Display the content for a note
"""
use MemexWeb, :component
alias Memex.Notes.Note
alias Phoenix.HTML
attr :note, Note, required: true
def note_content(assigns) do
~H"""
<div
id={"show-note-content-#{@note.id}"}
class="input input-primary h-128 min-h-128 inline-block overflow-x-hidden overflow-y-auto"
phx-hook="MaintainAttrs"
phx-update="ignore"
readonly
phx-no-format
><p class="inline"><%= add_links_to_content(@note.content) %></p></div>
"""
end
defp add_links_to_content(content) do
Regex.replace(
~r/\[\[([\p{L}\p{N}\-]+)\]\]/,
content,
fn _whole_match, slug ->
link =
HTML.Link.link(
"[[#{slug}]]",
to: Routes.note_show_path(Endpoint, :show, slug),
class: "link inline",
data: [qa: "note-link-#{slug}"]
)
|> HTML.Safe.to_iodata()
|> IO.iodata_to_binary()
"</p>#{link}<p class=\"inline\">"
end
)
|> String.replace("\n", "<br>")
|> HTML.raw()
end
end

View File

@ -4,7 +4,7 @@ defmodule MemexWeb.Components.NotesTableComponent do
"""
use MemexWeb, :live_component
alias Ecto.UUID
alias Memex.{Accounts.User, Notes.Note}
alias Memex.{Accounts.User, Notes, Notes.Note}
alias Phoenix.LiveView.{Rendered, Socket}
@impl true
@ -44,7 +44,8 @@ defmodule MemexWeb.Components.NotesTableComponent do
end
columns = [
%{label: gettext("slug"), key: :slug},
%{label: gettext("title"), key: :title},
%{label: gettext("content"), key: :content},
%{label: gettext("tags"), key: :tags},
%{label: gettext("visibility"), key: :visibility}
| columns
@ -88,34 +89,36 @@ defmodule MemexWeb.Components.NotesTableComponent do
@spec get_value_for_key(atom(), Note.t(), additional_data :: map()) ::
any() | {any(), Rendered.t()}
defp get_value_for_key(:slug, %{slug: slug}, _additional_data) do
assigns = %{slug: slug}
defp get_value_for_key(:title, %{id: id, title: title}, _additional_data) do
assigns = %{id: id, title: title}
slug_block = ~H"""
title_block = ~H"""
<.link
navigate={Routes.note_show_path(Endpoint, :show, @slug)}
navigate={Routes.note_show_path(Endpoint, :show, @id)}
class="link"
data-qa={"note-show-#{@slug}"}
data-qa={"note-show-#{@id}"}
>
<%= @slug %>
<%= @title %>
</.link>
"""
{slug, slug_block}
{title, title_block}
end
defp get_value_for_key(:content, %{content: content}, _additional_data) do
assigns = %{content: content}
content_block = ~H"""
<div class="truncate max-w-sm">
<%= @content %>
</div>
"""
{content, content_block}
end
defp get_value_for_key(:tags, %{tags: tags}, _additional_data) do
assigns = %{tags: tags}
~H"""
<div class="flex flex-wrap justify-center space-x-1">
<%= for tag <- @tags do %>
<.link patch={Routes.note_index_path(Endpoint, :search, tag)} class="link">
<%= tag %>
</.link>
<% end %>
</div>
"""
tags |> Notes.get_tags_string()
end
defp get_value_for_key(:actions, note, %{actions: actions}) do

View File

@ -1,145 +0,0 @@
defmodule MemexWeb.Components.PipelinesTableComponent do
@moduledoc """
A component that displays a list of pipelines
"""
use MemexWeb, :live_component
alias Ecto.UUID
alias Memex.{Accounts.User, Pipelines.Pipeline}
alias Phoenix.LiveView.{Rendered, Socket}
@impl true
@spec update(
%{
required(:id) => UUID.t(),
required(:current_user) => User.t(),
required(:pipelines) => [Pipeline.t()],
optional(any()) => any()
},
Socket.t()
) :: {:ok, Socket.t()}
def update(%{id: _id, pipelines: _pipelines, current_user: _current_user} = assigns, socket) do
socket =
socket
|> assign(assigns)
|> assign_new(:actions, fn -> [] end)
|> display_pipelines()
{:ok, socket}
end
defp display_pipelines(
%{
assigns: %{
pipelines: pipelines,
current_user: current_user,
actions: actions
}
} = socket
) do
columns =
if actions == [] or current_user |> is_nil() do
[]
else
[%{label: nil, key: :actions, sortable: false}]
end
columns = [
%{label: gettext("slug"), key: :slug},
%{label: gettext("description"), key: :description},
%{label: gettext("tags"), key: :tags},
%{label: gettext("visibility"), key: :visibility}
| columns
]
rows =
pipelines
|> Enum.map(fn pipeline ->
pipeline
|> get_row_data_for_pipeline(%{
columns: columns,
current_user: current_user,
actions: actions
})
end)
socket |> assign(columns: columns, rows: rows)
end
@impl true
def render(assigns) do
~H"""
<div class="w-full">
<.live_component
module={MemexWeb.Components.TableComponent}
id={@id}
columns={@columns}
rows={@rows}
/>
</div>
"""
end
@spec get_row_data_for_pipeline(Pipeline.t(), additional_data :: map()) :: map()
defp get_row_data_for_pipeline(pipeline, %{columns: columns} = additional_data) do
columns
|> Map.new(fn %{key: key} ->
{key, get_value_for_key(key, pipeline, additional_data)}
end)
end
@spec get_value_for_key(atom(), Pipeline.t(), additional_data :: map()) ::
any() | {any(), Rendered.t()}
defp get_value_for_key(:slug, %{slug: slug}, _additional_data) do
assigns = %{slug: slug}
slug_block = ~H"""
<.link
navigate={Routes.pipeline_show_path(Endpoint, :show, @slug)}
class="link"
data-qa={"pipeline-show-#{@slug}"}
>
<%= @slug %>
</.link>
"""
{slug, slug_block}
end
defp get_value_for_key(:description, %{description: description}, _additional_data) do
assigns = %{description: description}
description_block = ~H"""
<div class="truncate max-w-sm">
<%= @description %>
</div>
"""
{description, description_block}
end
defp get_value_for_key(:tags, %{tags: tags}, _additional_data) do
assigns = %{tags: tags}
~H"""
<div class="flex flex-wrap justify-center space-x-1">
<%= for tag <- @tags do %>
<.link patch={Routes.pipeline_index_path(Endpoint, :search, tag)} class="link">
<%= tag %>
</.link>
<% end %>
</div>
"""
end
defp get_value_for_key(:actions, pipeline, %{actions: actions}) do
assigns = %{actions: actions, pipeline: pipeline}
~H"""
<div class="flex justify-center items-center space-x-4">
<%= render_slot(@actions, @pipeline) %>
</div>
"""
end
defp get_value_for_key(key, pipeline, _additional_data), do: pipeline |> Map.get(key)
end

View File

@ -1,45 +0,0 @@
defmodule MemexWeb.Components.StepContent do
@moduledoc """
Display the content for a step
"""
use MemexWeb, :component
alias Memex.Pipelines.Steps.Step
alias Phoenix.HTML
attr :step, Step, required: true
def step_content(assigns) do
~H"""
<div
id={"show-step-content-#{@step.id}"}
class="input input-primary h-32 min-h-32 inline-block overflow-x-hidden overflow-y-auto"
phx-hook="MaintainAttrs"
phx-update="ignore"
readonly
phx-no-format
><p class="inline"><%= add_links_to_content(@step.content) %></p></div>
"""
end
defp add_links_to_content(content) do
Regex.replace(
~r/\[\[([\p{L}\p{N}\-]+)\]\]/,
content,
fn _whole_match, slug ->
link =
HTML.Link.link(
"[[#{slug}]]",
to: Routes.context_show_path(Endpoint, :show, slug),
class: "link inline",
data: [qa: "step-context-#{slug}"]
)
|> HTML.Safe.to_iodata()
|> IO.iodata_to_binary()
"</p>#{link}<p class=\"inline\">"
end
)
|> String.replace("\n", "<br>")
|> HTML.raw()
end
end

View File

@ -20,7 +20,7 @@ defmodule MemexWeb.Components.Topbar do
navigate={Routes.live_path(Endpoint, HomeLive)}
class="mx-2 my-1 leading-5 text-xl text-primary-400 hover:underline"
>
<%= gettext("memEx") %>
<%= gettext("memex") %>
</.link>
<%= if @title_content do %>
@ -65,7 +65,7 @@ defmodule MemexWeb.Components.Topbar do
<li class="mx-2 my-1 border-left border border-primary-700"></li>
<%= if @current_user do %>
<%= if @current_user |> Accounts.is_already_admin?() do %>
<%= if @current_user.role == :admin do %>
<li class="mx-2 my-1">
<.link
navigate={Routes.invite_index_path(Endpoint, :index)}

View File

@ -22,15 +22,15 @@ defmodule MemexWeb.Components.UserCard do
<%= if @user.confirmed_at |> is_nil() do %>
<%= gettext("email unconfirmed") %>
<% else %>
<p>
<%= gettext("user confirmed on") %>
<%= @user.confirmed_at |> display_datetime() %>
</p>
<%= gettext(
"user was confirmed at %{relative_datetime}",
relative_datetime: @user.confirmed_at |> display_datetime()
) %>
<% end %>
</p>
<p>
<%= gettext("user registered on") %>
<%= gettext("User registered on") %>
<%= @user.inserted_at |> display_datetime() %>
</p>
</h3>

View File

@ -2,6 +2,7 @@ defmodule MemexWeb.UserRegistrationController do
use MemexWeb, :controller
import MemexWeb.Gettext
alias Memex.{Accounts, Invites}
alias Memex.Accounts.User
alias MemexWeb.HomeLive
def new(conn, %{"invite" => invite_token}) do
@ -29,7 +30,7 @@ defmodule MemexWeb.UserRegistrationController do
# renders new user registration page
defp render_new(conn, invite \\ nil) do
render(conn, "new.html",
changeset: Accounts.change_user_registration(),
changeset: Accounts.change_user_registration(%User{}),
invite: invite,
page_title: gettext("register")
)

View File

@ -6,7 +6,7 @@ defmodule MemexWeb.UserResetPasswordController do
plug :get_user_by_reset_password_token when action in [:edit, :update]
def new(conn, _params) do
render(conn, "new.html", page_title: gettext("forgot your password?"))
render(conn, "new.html", page_title: gettext("Forgot your password?"))
end
def create(conn, %{"user" => %{"email" => email}}) do

View File

@ -20,7 +20,7 @@ defmodule MemexWeb.UserSessionController do
def delete(conn, _params) do
conn
|> put_flash(:info, dgettext("prompts", "logged out successfully."))
|> put_flash(:info, dgettext("prompts", "Logged out successfully."))
|> UserAuth.log_out_user()
end
end

View File

@ -4,8 +4,8 @@ defmodule MemexWeb.ContextLive.FormComponent do
alias Memex.Contexts
@impl true
def update(%{context: context, current_user: current_user} = assigns, socket) do
changeset = Contexts.change_context(context, current_user)
def update(%{context: context} = assigns, socket) do
changeset = Contexts.change_context(context)
{:ok,
socket
@ -14,52 +14,39 @@ defmodule MemexWeb.ContextLive.FormComponent do
end
@impl true
def handle_event(
"validate",
%{"context" => context_params},
%{assigns: %{context: context, current_user: current_user}} = socket
) do
def handle_event("validate", %{"context" => context_params}, socket) do
changeset =
context
|> Contexts.change_context(context_params, current_user)
socket.assigns.context
|> Contexts.change_context(context_params)
|> Map.put(:action, :validate)
{:noreply, assign(socket, :changeset, changeset)}
end
def handle_event("save", %{"context" => context_params}, %{assigns: %{action: action}} = socket) do
save_context(socket, action, context_params)
def handle_event("save", %{"context" => context_params}, socket) do
save_context(socket, socket.assigns.action, context_params)
end
defp save_context(
%{assigns: %{context: context, return_to: return_to, current_user: current_user}} =
socket,
:edit,
context_params
) do
case Contexts.update_context(context, context_params, current_user) do
{:ok, %{slug: slug}} ->
defp save_context(socket, :edit, context_params) do
case Contexts.update_context(socket.assigns.context, context_params) do
{:ok, _context} ->
{:noreply,
socket
|> put_flash(:info, gettext("%{slug} saved", slug: slug))
|> push_navigate(to: return_to)}
|> put_flash(:info, "context updated successfully")
|> push_navigate(to: socket.assigns.return_to)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, :changeset, changeset)}
end
end
defp save_context(
%{assigns: %{return_to: return_to, current_user: current_user}} = socket,
:new,
context_params
) do
case Contexts.create_context(context_params, current_user) do
{:ok, %{slug: slug}} ->
defp save_context(socket, :new, context_params) do
case Contexts.create_context(context_params) do
{:ok, _context} ->
{:noreply,
socket
|> put_flash(:info, gettext("%{slug} created", slug: slug))
|> push_navigate(to: return_to)}
|> put_flash(:info, "context created successfully")
|> push_navigate(to: socket.assigns.return_to)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, changeset: changeset)}

View File

@ -1,4 +1,6 @@
<div class="h-full flex flex-col justify-start items-stretch space-y-4">
<div>
<h2><%= @title %></h2>
<.form
:let={f}
for={@changeset}
@ -6,42 +8,27 @@
phx-target={@myself}
phx-change="validate"
phx-submit="save"
phx-debounce="300"
class="flex flex-col justify-start items-stretch space-y-4"
>
<%= text_input(f, :slug,
class: "input input-primary",
placeholder: gettext("slug")
) %>
<%= error_tag(f, :slug) %>
<%= label(f, :title) %>
<%= text_input(f, :title) %>
<%= error_tag(f, :title) %>
<%= textarea(f, :content,
id: "context-form-content",
class: "input input-primary h-64 min-h-64",
phx_hook: "MaintainAttrs",
phx_update: "ignore",
placeholder: gettext("use [[note-slug]] to link to a note")
) %>
<%= label(f, :content) %>
<%= textarea(f, :content) %>
<%= error_tag(f, :content) %>
<%= text_input(f, :tags_string,
id: "tags-input",
class: "input input-primary",
placeholder: gettext("tag1,tag2")
) %>
<%= error_tag(f, :tags_string) %>
<%= label(f, :tag) %>
<%= multiple_select(f, :tag, "Option 1": "option1", "Option 2": "option2") %>
<%= error_tag(f, :tag) %>
<div class="flex justify-center items-stretch space-x-4">
<%= label(f, :visibility) %>
<%= select(f, :visibility, Ecto.Enum.values(Memex.Contexts.Context, :visibility),
class: "grow input input-primary",
prompt: gettext("select privacy")
prompt: "Choose a value"
) %>
<%= submit(dgettext("actions", "save"),
phx_disable_with: gettext("saving..."),
class: "mx-auto btn btn-primary"
) %>
</div>
<%= error_tag(f, :visibility) %>
<div>
<%= submit("Save", phx_disable_with: "Saving...") %>
</div>
</.form>
</div>

View File

@ -1,89 +1,46 @@
defmodule MemexWeb.ContextLive.Index do
use MemexWeb, :live_view
alias Memex.{Accounts.User, Contexts, Contexts.Context}
alias Memex.Contexts
alias Memex.Contexts.Context
@impl true
def mount(%{"search" => search}, _session, socket) do
{:ok, socket |> assign(search: search) |> display_contexts()}
end
def mount(_params, _session, socket) do
{:ok, socket |> assign(search: nil) |> display_contexts()}
{:ok, assign(socket, :contexts, list_contexts())}
end
@impl true
def handle_params(params, _url, %{assigns: %{live_action: live_action}} = socket) do
{:noreply, apply_action(socket, live_action, params)}
def handle_params(params, _url, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end
defp apply_action(%{assigns: %{current_user: current_user}} = socket, :edit, %{"slug" => slug}) do
%{slug: slug} = context = Contexts.get_context_by_slug(slug, current_user)
defp apply_action(socket, :edit, %{"id" => id}) do
socket
|> assign(page_title: gettext("edit %{slug}", slug: slug))
|> assign(context: context)
|> assign(:page_title, "edit context")
|> assign(:context, Contexts.get_context!(id))
end
defp apply_action(%{assigns: %{current_user: %{id: current_user_id}}} = socket, :new, _params) do
defp apply_action(socket, :new, _params) do
socket
|> assign(page_title: gettext("new context"))
|> assign(context: %Context{visibility: :private, user_id: current_user_id})
|> assign(:page_title, "new context")
|> assign(:context, %Context{})
end
defp apply_action(socket, :index, _params) do
socket
|> assign(page_title: gettext("contexts"))
|> assign(search: nil)
|> assign(context: nil)
|> display_contexts()
end
defp apply_action(socket, :search, %{"search" => search}) do
socket
|> assign(page_title: gettext("contexts"))
|> assign(search: search)
|> assign(context: nil)
|> display_contexts()
|> assign(:page_title, "listing contexts")
|> assign(:context, nil)
end
@impl true
def handle_event("delete", %{"id" => id}, %{assigns: %{current_user: current_user}} = socket) do
context = Contexts.get_context!(id, current_user)
{:ok, %{slug: slug}} = Contexts.delete_context(context, current_user)
def handle_event("delete", %{"id" => id}, socket) do
context = Contexts.get_context!(id)
{:ok, _} = Contexts.delete_context(context)
socket =
socket
|> assign(contexts: Contexts.list_contexts(current_user))
|> put_flash(:info, gettext("%{slug} deleted", slug: slug))
{:noreply, socket}
{:noreply, assign(socket, :contexts, list_contexts())}
end
@impl true
def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do
{:noreply, socket |> push_patch(to: Routes.context_index_path(Endpoint, :index))}
defp list_contexts do
Contexts.list_contexts()
end
def handle_event("search", %{"search" => %{"search_term" => search_term}}, socket) do
{:noreply,
socket |> push_patch(to: Routes.context_index_path(Endpoint, :search, search_term))}
end
defp display_contexts(%{assigns: %{current_user: current_user, search: search}} = socket)
when not (current_user |> is_nil()) do
socket |> assign(contexts: Contexts.list_contexts(search, current_user))
end
defp display_contexts(%{assigns: %{search: search}} = socket) do
socket |> assign(contexts: Contexts.list_public_contexts(search))
end
@spec is_owner_or_admin?(Context.t(), User.t()) :: boolean()
defp is_owner_or_admin?(%{user_id: user_id}, %{id: user_id}), do: true
defp is_owner_or_admin?(_context, %{role: :admin}), do: true
defp is_owner_or_admin?(_context, _other_user), do: false
@spec is_owner?(Context.t(), User.t()) :: boolean()
defp is_owner?(%{user_id: user_id}, %{id: user_id}), do: true
defp is_owner?(_context, _other_user), do: false
end

View File

@ -1,71 +1,10 @@
<div class="mx-auto flex flex-col justify-center items-start space-y-4 max-w-3xl">
<h1 class="text-xl">
<%= gettext("contexts") %>
</h1>
<.form
:let={f}
for={:search}
phx-change="search"
phx-submit="search"
class="self-stretch flex flex-col items-stretch"
>
<%= text_input(f, :search_term,
class: "input input-primary",
value: @search,
phx_debounce: 300,
placeholder: gettext("search")
) %>
</.form>
<%= if @contexts |> Enum.empty?() do %>
<h1 class="self-center text-primary-500">
<%= gettext("no contexts found") %>
</h1>
<% else %>
<.live_component
module={MemexWeb.Components.ContextsTableComponent}
id="contexts-index-table"
current_user={@current_user}
contexts={@contexts}
>
<:actions :let={context}>
<%= if is_owner?(context, @current_user) do %>
<.link
patch={Routes.context_index_path(@socket, :edit, context.slug)}
data-qa={"context-edit-#{context.id}"}
>
<%= dgettext("actions", "edit") %>
</.link>
<% end %>
<%= if is_owner_or_admin?(context, @current_user) do %>
<.link
href="#"
phx-click="delete"
phx-value-id={context.id}
data-confirm={dgettext("prompts", "are you sure?")}
data-qa={"delete-context-#{context.id}"}
>
<%= dgettext("actions", "delete") %>
</.link>
<% end %>
</:actions>
</.live_component>
<% end %>
<%= if @current_user do %>
<.link patch={Routes.context_index_path(@socket, :new)} class="self-end btn btn-primary">
<%= dgettext("actions", "new context") %>
</.link>
<% end %>
</div>
<h1>listing contexts</h1>
<%= if @live_action in [:new, :edit] do %>
<.modal return_to={Routes.context_index_path(@socket, :index)}>
<.live_component
module={MemexWeb.ContextLive.FormComponent}
id={@context.id || :new}
current_user={@current_user}
title={@page_title}
action={@live_action}
context={@context}
@ -73,3 +12,55 @@
/>
</.modal>
<% end %>
<table>
<thead>
<tr>
<th>Title</th>
<th>Content</th>
<th>Tag</th>
<th>Visibility</th>
<th></th>
</tr>
</thead>
<tbody id="contexts">
<%= for context <- @contexts do %>
<tr id={"context-#{context.id}"}>
<td><%= context.title %></td>
<td><%= context.content %></td>
<td><%= context.tag %></td>
<td><%= context.visibility %></td>
<td>
<span>
<.link navigate={Routes.context_show_path(@socket, :show, context)}>
<%= dgettext("actions", "show") %>
</.link>
</span>
<span>
<.link patch={Routes.context_index_path(@socket, :edit, context)}>
<%= dgettext("actions", "edit") %>
</.link>
</span>
<span>
<.link
href="#"
phx-click="delete"
phx-value-id={context.id}
data-confirm={dgettext("prompts", "are you sure?")}
>
<%= dgettext("actions", "delete") %>
</.link>
</span>
</td>
</tr>
<% end %>
</tbody>
</table>
<span>
<.link patch={Routes.context_index_path(@socket, :new)}>
<%= dgettext("actions", "new context") %>
</.link>
</span>

View File

@ -1,7 +1,7 @@
defmodule MemexWeb.ContextLive.Show do
use MemexWeb, :live_view
import MemexWeb.Components.ContextContent
alias Memex.{Accounts.User, Contexts, Contexts.Context}
alias Memex.Contexts
@impl true
def mount(_params, _session, socket) do
@ -9,50 +9,13 @@ defmodule MemexWeb.ContextLive.Show do
end
@impl true
def handle_params(
%{"slug" => slug},
_,
%{assigns: %{live_action: live_action, current_user: current_user}} = socket
) do
context =
case Contexts.get_context_by_slug(slug, current_user) do
nil -> raise MemexWeb.NotFoundError, gettext("%{slug} could not be found", slug: slug)
context -> context
end
socket =
def handle_params(%{"id" => id}, _, socket) do
{:noreply,
socket
|> assign(:page_title, page_title(live_action, context))
|> assign(:context, context)
{:noreply, socket}
|> assign(:page_title, page_title(socket.assigns.live_action))
|> assign(:context, Contexts.get_context!(id))}
end
@impl true
def handle_event(
"delete",
_params,
%{assigns: %{context: context, current_user: current_user}} = socket
) do
{:ok, %{slug: slug}} = Contexts.delete_context(context, current_user)
socket =
socket
|> put_flash(:info, gettext("%{slug} deleted", slug: slug))
|> push_navigate(to: Routes.context_index_path(Endpoint, :index))
{:noreply, socket}
end
defp page_title(:show, %{slug: slug}), do: slug
defp page_title(:edit, %{slug: slug}), do: gettext("edit %{slug}", slug: slug)
@spec is_owner_or_admin?(Context.t(), User.t()) :: boolean()
defp is_owner_or_admin?(%{user_id: user_id}, %{id: user_id}), do: true
defp is_owner_or_admin?(_context, %{role: :admin}), do: true
defp is_owner_or_admin?(_context, _other_user), do: false
@spec is_owner?(Context.t(), User.t()) :: boolean()
defp is_owner?(%{user_id: user_id}, %{id: user_id}), do: true
defp is_owner?(_context, _other_user), do: false
defp page_title(:show), do: "show context"
defp page_title(:edit), do: "edit context"
end

View File

@ -1,58 +1,48 @@
<div class="mx-auto flex flex-col justify-center items-stretch space-y-4 max-w-3xl">
<h1 class="text-xl">
<%= @context.slug %>
</h1>
<div class="flex flex-wrap space-x-1">
<%= for tag <- @context.tags do %>
<.link navigate={Routes.context_index_path(Endpoint, :search, tag)} class="link">
<%= tag %>
</.link>
<% end %>
</div>
<.context_content context={@context} />
<p class="self-end">
<%= gettext("Visibility: %{visibility}", visibility: @context.visibility) %>
</p>
<div class="self-end flex space-x-4">
<.link class="btn btn-primary" navigate={Routes.context_index_path(@socket, :index)}>
<%= dgettext("actions", "back") %>
</.link>
<%= if is_owner?(@context, @current_user) do %>
<.link
class="btn btn-primary"
patch={Routes.context_show_path(@socket, :edit, @context.slug)}
>
<%= dgettext("actions", "edit") %>
</.link>
<% end %>
<%= if is_owner_or_admin?(@context, @current_user) do %>
<button
type="button"
class="btn btn-primary"
phx-click="delete"
data-confirm={dgettext("prompts", "are you sure?")}
data-qa={"delete-context-#{@context.id}"}
>
<%= dgettext("actions", "delete") %>
</button>
<% end %>
</div>
</div>
<h1>show context</h1>
<%= if @live_action in [:edit] do %>
<.modal return_to={Routes.context_show_path(@socket, :show, @context.slug)}>
<.modal return_to={Routes.context_show_path(@socket, :show, @context)}>
<.live_component
module={MemexWeb.ContextLive.FormComponent}
id={@context.id}
current_user={@current_user}
title={@page_title}
action={@live_action}
context={@context}
return_to={Routes.context_show_path(@socket, :show, @context.slug)}
return_to={Routes.context_show_path(@socket, :show, @context)}
/>
</.modal>
<% end %>
<ul>
<li>
<strong>Title:</strong>
<%= @context.title %>
</li>
<li>
<strong>Content:</strong>
<%= @context.content %>
</li>
<li>
<strong>Tag:</strong>
<%= @context.tag %>
</li>
<li>
<strong>Visibility:</strong>
<%= @context.visibility %>
</li>
</ul>
<span>
<.link patch={Routes.context_show_path(@socket, :edit, @context)} class="button">
<%= dgettext("actions", "edit") %>
</.link>
</span>
|
<span>
<.link navigate={Routes.context_index_path(@socket, :index)}>
<%= dgettext("actions", "Back") %>
</.link>
</span>

View File

@ -1,12 +0,0 @@
defmodule MemexWeb.FaqLive do
@moduledoc """
Liveview for the faq page
"""
use MemexWeb, :live_view
@impl true
def mount(_params, _session, socket) do
{:ok, socket |> assign(page_title: gettext("faq"))}
end
end

View File

@ -1,145 +0,0 @@
<div class="mx-auto flex flex-col justify-center items-stretch space-y-8 text-center max-w-3xl">
<h1 class="title text-primary-400 text-2xl">
<%= gettext("faq") %>
</h1>
<hr class="hr" />
<ul class="flex flex-col justify-center items-stretch space-y-8">
<li class="flex flex-col justify-center items-stretch space-y-2">
<b class="whitespace-nowrap">
<%= gettext("what is this?") %>
</b>
<p>
<%= gettext(
"this is a memex, used to document not just your notes, but also your perspectives and processes."
) %>
</p>
<p>
<%= gettext("some things that this memex is very loosely inspired by:") %>
</p>
<ul class="list-disc flex flex-col justify-center items-center space-y-2">
<li>
<.link
href="https://en.wikipedia.org/wiki/Memex"
class="flex flex-row justify-center items-center space-x-2 link"
target="_blank"
rel="noopener noreferrer"
>
<%= gettext("memex") %>
</.link>
</li>
<li>
<.link
href="https://en.wikipedia.org/wiki/Zettelkasten"
class="flex flex-row justify-center items-center space-x-2 link"
target="_blank"
rel="noopener noreferrer"
>
<%= gettext("zettelkasten") %>
</.link>
</li>
<li>
<.link
href="https://en.wikipedia.org/wiki/Org-mode"
class="flex flex-row justify-center items-center space-x-2 link"
target="_blank"
rel="noopener noreferrer"
>
<%= gettext("org-mode") %>
</.link>
</li>
</ul>
</li>
<li class="flex flex-col justify-center items-stretch space-y-2">
<b class="whitespace-nowrap">
<%= gettext("why split up into notes, contexts and pipelines?") %>
</b>
<p>
<%= gettext(
"i really admired the idea of a zettelkasten, especially with org-mode backlinks, however I felt like my notes would immediately become too messy by just putting everything into a single hierarchy."
) %>
</p>
<p>
<%= gettext(
"i wanted to separate between a personal dictionary of concepts and then my thought processes that are built off of my experiences and life lessons. these are notes, and contexts, respectively."
) %>
</p>
<p>
<%= gettext(
"finally, i wanted to externalize the processes for common situations that use these thought processes at discrete steps. these are pipelines!"
) %>
</p>
</li>
<li class="flex flex-col justify-center items-stretch space-y-2">
<b class="whitespace-nowrap">
<%= gettext("what should my notes be like?") %>
</b>
<p>
<%= gettext(
"in my opinion, notes should be written by any of the discrete objects or concepts that are meaningful to you in your life."
) %>
</p>
<p>
<%= gettext(
"spoons? probably not. a particular brand of spoons that you really like? why not :)"
) %>
</p>
</li>
<li class="flex flex-col justify-center items-stretch space-y-2">
<b class="whitespace-nowrap">
<%= gettext("what should my contexts be like?") %>
</b>
<p>
<%= gettext("in my opinion, contexts should be like single-topic blog posts.") %>
</p>
<p>
<%= gettext(
"for instance, a good context could be what makes some physical designs spark joy for you, and in that context you could backlink to the spoon note as an example of how it fits nicely into your hand."
) %>
</p>
</li>
<li class="flex flex-col justify-center items-stretch space-y-2">
<b class="whitespace-nowrap">
<%= gettext("what should my pipelines be like?") %>
</b>
<p>
<%= gettext(
"in my opinion, pipelines should be pretty lightweight, and just backlink to contexts to provide most of the heavy lifting."
) %>
</p>
<p>
<%= gettext(
"for instance, a pipeline for buying an object could have a step where you consider how much it sparks joy, and it could backlink to the physical designs context, maybe with some notes about how it applies in this case."
) %>
</p>
</li>
<li class="flex flex-col justify-center items-stretch space-y-2">
<b class="whitespace-nowrap">
<%= gettext("how many people should i invite?") %>
</b>
<p>
<%= gettext(
"while memEx fully supports multiple users, each memEx instance should be treated as a single cohesive and collaborative document."
) %>
</p>
<p>
<%= gettext(
"note, context and pipeline slugs must be unique, and you are free to backlink to notes not written by you."
) %>
</p>
<p>
<%= gettext(
"so, i'd recommend inviting anyone you'd like to work on your collective memEx. however, when in doubt, hopefully setting up a new instance is easy enough. if it isn't, then feel free to let me know :)"
) %>
</p>
</li>
</ul>
</div>

View File

@ -3,15 +3,43 @@ defmodule MemexWeb.HomeLive do
Liveview for the main home page
"""
@version Mix.Project.config()[:version]
use MemexWeb, :live_view
alias Memex.Accounts
alias MemexWeb.{Endpoint, FaqLive}
@impl true
def mount(_params, _session, socket) do
admins = Accounts.list_users_by_role(:admin)
{:ok, socket |> assign(page_title: gettext("home"), admins: admins, version: @version)}
{:ok, socket |> assign(page_title: gettext("Home"), query: "", results: %{}, admins: admins)}
end
@impl true
def handle_event("suggest", %{"q" => query}, socket) do
{:noreply, socket |> assign(results: search(query), query: query)}
end
@impl true
def handle_event("search", %{"q" => query}, socket) do
case search(query) do
%{^query => vsn} ->
{:noreply, socket |> redirect(external: "https://hexdocs.pm/#{query}/#{vsn}")}
_ ->
{:noreply,
socket
|> put_flash(:error, "No dependencies found matching \"#{query}\"")
|> assign(results: %{}, query: query)}
end
end
defp search(query) do
if not MemexWeb.Endpoint.config(:code_reloader) do
raise "action disabled when not in development"
end
for {app, desc, vsn} <- Application.started_applications(),
app = to_string(app),
String.starts_with?(app, query) and not List.starts_with?(desc, ~c"ERTS"),
into: %{},
do: {app, vsn}
end
end

View File

@ -1,12 +1,13 @@
<div class="mx-auto flex flex-col justify-center items-stretch space-y-4 max-w-3xl">
<h1 class="title text-primary-400 text-2xl text-center">
<%= gettext("memEx") %>
<div class="flex flex-col justify-center items-center text-center space-y-4">
<h1 class="title text-primary-400 text-2xl">
<%= gettext("memex") %>
</h1>
<hr class="hr" />
<ul class="flex flex-col space-y-4 text-center">
<li class="flex flex-col justify-center items-center space-y-2">
<li class="flex flex-col justify-center items-center
space-y-2">
<b class="whitespace-nowrap">
<%= gettext("notes:") %>
</b>
@ -15,7 +16,8 @@
</p>
</li>
<li class="flex flex-col justify-center items-center space-y-2">
<li class="flex flex-col justify-center items-center
space-y-2">
<b class="whitespace-nowrap">
<%= gettext("contexts:") %>
</b>
@ -24,7 +26,8 @@
</p>
</li>
<li class="flex flex-col justify-center items-center space-y-2">
<li class="flex flex-col justify-center items-center
space-y-2">
<b class="whitespace-nowrap">
<%= gettext("pipelines:") %>
</b>
@ -32,15 +35,6 @@
<%= gettext("document your processes, attaching contexts to each step") %>
</p>
</li>
<li class="flex flex-col justify-center items-center space-y-2">
<.link
navigate={Routes.live_path(Endpoint, FaqLive)}
class="link title text-primary-400 text-lg"
>
<%= gettext("read more on how to use %{name}", name: "memEx") %>
</.link>
</li>
</ul>
<hr class="hr" />
@ -50,7 +44,8 @@
<%= gettext("features") %>
</h2>
<li class="flex flex-col justify-center items-center space-y-2">
<li class="flex flex-col justify-center items-center
space-y-2">
<b class="whitespace-nowrap">
<%= gettext("multi-user:") %>
</b>
@ -59,7 +54,8 @@
</p>
</li>
<li class="flex flex-col justify-center items-center space-y-2">
<li class="flex flex-col justify-center items-center
space-y-2">
<b class="whitespace-nowrap">
<%= gettext("privacy:") %>
</b>
@ -68,7 +64,8 @@
</p>
</li>
<li class="flex flex-col justify-center items-center space-y-2">
<li class="flex flex-col justify-center items-center
space-y-2">
<b class="whitespace-nowrap">
<%= gettext("convenient:") %>
</b>
@ -91,13 +88,16 @@
</b>
<p>
<%= if @admins |> Enum.empty?() do %>
<.link href={Routes.user_registration_path(Endpoint, :new)} class="link">
<%= dgettext("prompts", "register to setup %{name}", name: "memEx") %>
<.link
href={Routes.user_registration_path(MemexWeb.Endpoint, :new)}
class="hover:underline"
>
<%= dgettext("prompts", "register to setup %{name}", name: "memex") %>
</.link>
<% else %>
<div class="flex flex-wrap justify-center space-x-2">
<%= for admin <- @admins do %>
<a class="link" href={"mailto:#{admin.email}"}>
<a class="hover:underline" href={"mailto:#{admin.email}"}>
<%= admin.email %>
</a>
<% end %>
@ -109,7 +109,7 @@
<li class="flex flex-row justify-center space-x-2">
<b><%= gettext("registration:") %></b>
<p>
<%= Application.get_env(:memex, Endpoint)[:registration]
<%= Application.get_env(:memex, MemexWeb.Endpoint)[:registration]
|> case do
"public" -> gettext("public signups")
_ -> gettext("invite only")
@ -120,12 +120,12 @@
<li class="flex flex-row justify-center items-center space-x-2">
<b><%= gettext("version:") %></b>
<.link
href="https://gitea.bubbletea.dev/shibao/memEx/src/branch/stable/changelog.md"
class="flex flex-row justify-center items-center space-x-2 link"
href="https://gitea.bubbletea.dev/shibao/memex/src/branch/stable/CHANGELOG.md"
class="flex flex-row justify-center items-center space-x-2 hover:underline"
target="_blank"
rel="noopener noreferrer"
>
<p><%= @version %></p>
<p>0.1.0</p>
<i class="fas fa-md fa-info-circle"></i>
</.link>
</li>
@ -140,8 +140,8 @@
<li class="flex flex-col justify-center space-x-2">
<.link
href="https://gitea.bubbletea.dev/shibao/memEx"
class="flex flex-row justify-center items-center space-x-2 link"
href="https://gitea.bubbletea.dev/shibao/memex"
class="flex flex-row justify-center items-center space-x-2 hover:underline"
target="_blank"
rel="noopener noreferrer"
>
@ -151,8 +151,8 @@
</li>
<li class="flex flex-col justify-center space-x-2">
<.link
href="https://weblate.bubbletea.dev/engage/memEx"
class="flex flex-row justify-center items-center space-x-2 link"
href="https://weblate.bubbletea.dev/engage/memex"
class="flex flex-row justify-center items-center space-x-2 hover:underline"
target="_blank"
rel="noopener noreferrer"
>
@ -162,8 +162,8 @@
</li>
<li class="flex flex-col justify-center space-x-2">
<.link
href="https://gitea.bubbletea.dev/shibao/memEx/issues/new"
class="flex flex-row justify-center items-center space-x-2 link"
href="https://gitea.bubbletea.dev/shibao/memex/issues/new"
class="flex flex-row justify-center items-center space-x-2 hover:underline"
target="_blank"
rel="noopener noreferrer"
>

View File

@ -37,10 +37,10 @@ defmodule MemexWeb.NoteLive.FormComponent do
note_params
) do
case Notes.update_note(note, note_params, current_user) do
{:ok, %{slug: slug}} ->
{:ok, %{title: title}} ->
{:noreply,
socket
|> put_flash(:info, gettext("%{slug} saved", slug: slug))
|> put_flash(:info, gettext("%{title} saved", title: title))
|> push_navigate(to: return_to)}
{:error, %Ecto.Changeset{} = changeset} ->
@ -54,10 +54,10 @@ defmodule MemexWeb.NoteLive.FormComponent do
note_params
) do
case Notes.create_note(note_params, current_user) do
{:ok, %{slug: slug}} ->
{:ok, %{title: title}} ->
{:noreply,
socket
|> put_flash(:info, gettext("%{slug} created", slug: slug))
|> put_flash(:info, gettext("%{title} created", title: title))
|> push_navigate(to: return_to)}
{:error, %Ecto.Changeset{} = changeset} ->

View File

@ -9,11 +9,11 @@
phx-debounce="300"
class="flex flex-col justify-start items-stretch space-y-4"
>
<%= text_input(f, :slug,
<%= text_input(f, :title,
class: "input input-primary",
placeholder: gettext("slug")
placeholder: gettext("title")
) %>
<%= error_tag(f, :slug) %>
<%= error_tag(f, :title) %>
<%= textarea(f, :content,
id: "note-form-content",
@ -27,7 +27,9 @@
<%= text_input(f, :tags_string,
id: "tags-input",
class: "input input-primary",
placeholder: gettext("tag1,tag2")
placeholder: gettext("tag1,tag2"),
phx_update: "ignore",
value: Notes.get_tags_string(@changeset)
) %>
<%= error_tag(f, :tags_string) %>

View File

@ -1,13 +1,9 @@
defmodule MemexWeb.NoteLive.Index do
use MemexWeb, :live_view
alias Memex.{Accounts.User, Notes, Notes.Note}
alias Memex.{Notes, Notes.Note}
@impl true
def mount(%{"search" => search}, _session, socket) do
{:ok, socket |> assign(search: search) |> display_notes()}
end
def mount(_params, _session, socket) do
def mount(params, _session, socket) do
{:ok, socket |> assign(search: nil) |> display_notes()}
end
@ -16,23 +12,23 @@ defmodule MemexWeb.NoteLive.Index do
{:noreply, apply_action(socket, live_action, params)}
end
defp apply_action(%{assigns: %{current_user: current_user}} = socket, :edit, %{"slug" => slug}) do
%{slug: slug} = note = Notes.get_note_by_slug(slug, current_user)
defp apply_action(%{assigns: %{current_user: current_user}} = socket, :edit, %{"id" => id}) do
%{title: title} = note = Notes.get_note!(id, current_user)
socket
|> assign(page_title: gettext("edit %{slug}", slug: slug))
|> assign(page_title: gettext("edit %{title}", title: title))
|> assign(note: note)
end
defp apply_action(%{assigns: %{current_user: %{id: current_user_id}}} = socket, :new, _params) do
socket
|> assign(page_title: gettext("new note"))
|> assign(note: %Note{visibility: :private, user_id: current_user_id})
|> assign(page_title: "new note")
|> assign(note: %Note{user_id: current_user_id})
end
defp apply_action(socket, :index, _params) do
socket
|> assign(page_title: gettext("notes"))
|> assign(page_title: "notes")
|> assign(search: nil)
|> assign(note: nil)
|> display_notes()
@ -40,7 +36,7 @@ defmodule MemexWeb.NoteLive.Index do
defp apply_action(socket, :search, %{"search" => search}) do
socket
|> assign(page_title: gettext("notes"))
|> assign(page_title: "notes")
|> assign(search: search)
|> assign(note: nil)
|> display_notes()
@ -48,13 +44,13 @@ defmodule MemexWeb.NoteLive.Index do
@impl true
def handle_event("delete", %{"id" => id}, %{assigns: %{current_user: current_user}} = socket) do
note = Notes.get_note!(id, current_user)
{:ok, %{slug: slug}} = Notes.delete_note(note, current_user)
%{title: title} = note = Notes.get_note!(id, current_user)
{:ok, _} = Notes.delete_note(note, current_user)
socket =
socket
|> assign(notes: Notes.list_notes(current_user))
|> put_flash(:info, gettext("%{slug} deleted", slug: slug))
|> put_flash(:info, gettext("%{title} deleted", title: title))
{:noreply, socket}
end
@ -76,13 +72,4 @@ defmodule MemexWeb.NoteLive.Index do
defp display_notes(%{assigns: %{search: search}} = socket) do
socket |> assign(notes: Notes.list_public_notes(search))
end
@spec is_owner_or_admin?(Note.t(), User.t()) :: boolean()
defp is_owner_or_admin?(%{user_id: user_id}, %{id: user_id}), do: true
defp is_owner_or_admin?(_context, %{role: :admin}), do: true
defp is_owner_or_admin?(_context, _other_user), do: false
@spec is_owner?(Note.t(), User.t()) :: boolean()
defp is_owner?(%{user_id: user_id}, %{id: user_id}), do: true
defp is_owner?(_context, _other_user), do: false
end

View File

@ -8,14 +8,10 @@
for={:search}
phx-change="search"
phx-submit="search"
phx-debounce="500"
class="self-stretch flex flex-col items-stretch"
>
<%= text_input(f, :search_term,
class: "input input-primary",
value: @search,
phx_debounce: 300,
placeholder: gettext("search")
) %>
<%= text_input(f, :search_term, class: "input input-primary") %>
</.form>
<%= if @notes |> Enum.empty?() do %>
@ -30,15 +26,13 @@
notes={@notes}
>
<:actions :let={note}>
<%= if is_owner?(note, @current_user) do %>
<%= if @current_user do %>
<.link
patch={Routes.note_index_path(@socket, :edit, note.slug)}
patch={Routes.note_index_path(@socket, :edit, note)}
data-qa={"note-edit-#{note.id}"}
>
<%= dgettext("actions", "edit") %>
</.link>
<% end %>
<%= if is_owner_or_admin?(note, @current_user) do %>
<.link
href="#"
phx-click="delete"

View File

@ -1,7 +1,7 @@
defmodule MemexWeb.NoteLive.Show do
use MemexWeb, :live_view
import MemexWeb.Components.NoteContent
alias Memex.{Accounts.User, Notes, Notes.Note}
alias Memex.Notes
@impl true
def mount(_params, _session, socket) do
@ -10,49 +10,16 @@ defmodule MemexWeb.NoteLive.Show do
@impl true
def handle_params(
%{"slug" => slug},
%{"id" => id},
_,
%{assigns: %{live_action: live_action, current_user: current_user}} = socket
) do
note =
case Notes.get_note_by_slug(slug, current_user) do
nil -> raise MemexWeb.NotFoundError, gettext("%{slug} could not be found", slug: slug)
note -> note
end
socket =
{:noreply,
socket
|> assign(:page_title, page_title(live_action, note))
|> assign(:note, note)
{:noreply, socket}
|> assign(:page_title, page_title(live_action))
|> assign(:note, Notes.get_note!(id, current_user))}
end
@impl true
def handle_event(
"delete",
_params,
%{assigns: %{note: note, current_user: current_user}} = socket
) do
{:ok, %{slug: slug}} = Notes.delete_note(note, current_user)
socket =
socket
|> put_flash(:info, gettext("%{slug} deleted", slug: slug))
|> push_navigate(to: Routes.note_index_path(Endpoint, :index))
{:noreply, socket}
end
defp page_title(:show, %{slug: slug}), do: slug
defp page_title(:edit, %{slug: slug}), do: gettext("edit %{slug}", slug: slug)
@spec is_owner_or_admin?(Note.t(), User.t()) :: boolean()
defp is_owner_or_admin?(%{user_id: user_id}, %{id: user_id}), do: true
defp is_owner_or_admin?(_context, %{role: :admin}), do: true
defp is_owner_or_admin?(_context, _other_user), do: false
@spec is_owner?(Note.t(), User.t()) :: boolean()
defp is_owner?(%{user_id: user_id}, %{id: user_id}), do: true
defp is_owner?(_context, _other_user), do: false
defp page_title(:show), do: "show note"
defp page_title(:edit), do: "edit note"
end

View File

@ -1,47 +1,37 @@
<div class="mx-auto flex flex-col justify-center items-stretch space-y-4 max-w-3xl">
<h1 class="text-xl">
<%= @note.slug %>
<%= @note.title %>
</h1>
<div class="flex flex-wrap space-x-1">
<%= for tag <- @note.tags do %>
<.link navigate={Routes.note_index_path(Endpoint, :search, tag)} class="link">
<%= tag %>
</.link>
<% end %>
</div>
<p><%= if @note.tags, do: @note.tags |> Enum.join(", ") %></p>
<.note_content note={@note} />
<textarea
id="show-note-content"
class="input input-primary h-128 min-h-128"
phx-hook="MaintainAttrs"
phx-update="ignore"
readonly
phx-no-format
><%= @note.content %></textarea>
<p class="self-end">
<%= gettext("Visibility: %{visibility}", visibility: @note.visibility) %>
</p>
<div class="self-end flex space-x-4">
<.link class="btn btn-primary" navigate={Routes.note_index_path(@socket, :index)}>
<%= dgettext("actions", "back") %>
<.link class="btn btn-primary" patch={Routes.note_index_path(@socket, :index)}>
<%= dgettext("actions", "Back") %>
</.link>
<%= if is_owner?(@note, @current_user) do %>
<.link class="btn btn-primary" patch={Routes.note_show_path(@socket, :edit, @note.slug)}>
<%= if @current_user do %>
<.link class="btn btn-primary" patch={Routes.note_show_path(@socket, :edit, @note)}>
<%= dgettext("actions", "edit") %>
</.link>
<% end %>
<%= if is_owner_or_admin?(@note, @current_user) do %>
<button
type="button"
class="btn btn-primary"
phx-click="delete"
data-confirm={dgettext("prompts", "are you sure?")}
data-qa={"delete-note-#{@note.id}"}
>
<%= dgettext("actions", "delete") %>
</button>
<% end %>
</div>
</div>
<%= if @live_action in [:edit] do %>
<.modal return_to={Routes.note_show_path(@socket, :show, @note.slug)}>
<.modal return_to={Routes.note_show_path(@socket, :show, @note)}>
<.live_component
module={MemexWeb.NoteLive.FormComponent}
id={@note.id}
@ -49,7 +39,7 @@
title={@page_title}
action={@live_action}
note={@note}
return_to={Routes.note_show_path(@socket, :show, @note.slug)}
return_to={Routes.note_show_path(@socket, :show, @note)}
/>
</.modal>
<% end %>

View File

@ -4,8 +4,8 @@ defmodule MemexWeb.PipelineLive.FormComponent do
alias Memex.Pipelines
@impl true
def update(%{pipeline: pipeline, current_user: current_user} = assigns, socket) do
changeset = Pipelines.change_pipeline(pipeline, current_user)
def update(%{pipeline: pipeline} = assigns, socket) do
changeset = Pipelines.change_pipeline(pipeline)
{:ok,
socket
@ -14,56 +14,39 @@ defmodule MemexWeb.PipelineLive.FormComponent do
end
@impl true
def handle_event(
"validate",
%{"pipeline" => pipeline_params},
%{assigns: %{pipeline: pipeline, current_user: current_user}} = socket
) do
def handle_event("validate", %{"pipeline" => pipeline_params}, socket) do
changeset =
pipeline
|> Pipelines.change_pipeline(pipeline_params, current_user)
socket.assigns.pipeline
|> Pipelines.change_pipeline(pipeline_params)
|> Map.put(:action, :validate)
{:noreply, assign(socket, :changeset, changeset)}
end
def handle_event(
"save",
%{"pipeline" => pipeline_params},
%{assigns: %{action: action}} = socket
) do
save_pipeline(socket, action, pipeline_params)
def handle_event("save", %{"pipeline" => pipeline_params}, socket) do
save_pipeline(socket, socket.assigns.action, pipeline_params)
end
defp save_pipeline(
%{assigns: %{pipeline: pipeline, return_to: return_to, current_user: current_user}} =
socket,
:edit,
pipeline_params
) do
case Pipelines.update_pipeline(pipeline, pipeline_params, current_user) do
{:ok, %{slug: slug}} ->
defp save_pipeline(socket, :edit, pipeline_params) do
case Pipelines.update_pipeline(socket.assigns.pipeline, pipeline_params) do
{:ok, _pipeline} ->
{:noreply,
socket
|> put_flash(:info, gettext("%{slug} saved", slug: slug))
|> push_navigate(to: return_to)}
|> put_flash(:info, "pipeline updated successfully")
|> push_navigate(to: socket.assigns.return_to)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, :changeset, changeset)}
end
end
defp save_pipeline(
%{assigns: %{return_to: return_to, current_user: current_user}} = socket,
:new,
pipeline_params
) do
case Pipelines.create_pipeline(pipeline_params, current_user) do
{:ok, %{slug: slug}} ->
defp save_pipeline(socket, :new, pipeline_params) do
case Pipelines.create_pipeline(pipeline_params) do
{:ok, _pipeline} ->
{:noreply,
socket
|> put_flash(:info, gettext("%{slug} created", slug: slug))
|> push_navigate(to: return_to)}
|> put_flash(:info, "pipeline created successfully")
|> push_navigate(to: socket.assigns.return_to)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, changeset: changeset)}

View File

@ -1,4 +1,6 @@
<div class="h-full flex flex-col justify-start items-stretch space-y-4">
<div>
<h2><%= @title %></h2>
<.form
:let={f}
for={@changeset}
@ -6,42 +8,23 @@
phx-target={@myself}
phx-change="validate"
phx-submit="save"
phx-debounce="300"
class="flex flex-col justify-start items-stretch space-y-4"
>
<%= text_input(f, :slug,
class: "input input-primary",
placeholder: gettext("slug")
) %>
<%= error_tag(f, :slug) %>
<%= label(f, :title) %>
<%= text_input(f, :title) %>
<%= error_tag(f, :title) %>
<%= textarea(f, :description,
id: "pipeline-form-description",
class: "input input-primary h-64 min-h-64",
phx_hook: "MaintainAttrs",
phx_update: "ignore",
placeholder: gettext("description")
) %>
<%= label(f, :description) %>
<%= textarea(f, :description) %>
<%= error_tag(f, :description) %>
<%= text_input(f, :tags_string,
id: "tags-input",
class: "input input-primary",
placeholder: gettext("tag1,tag2")
) %>
<%= error_tag(f, :tags_string) %>
<div class="flex justify-center items-stretch space-x-4">
<%= label(f, :visibility) %>
<%= select(f, :visibility, Ecto.Enum.values(Memex.Pipelines.Pipeline, :visibility),
class: "grow input input-primary",
prompt: gettext("select privacy")
prompt: "Choose a value"
) %>
<%= submit(dgettext("actions", "save"),
phx_disable_with: gettext("saving..."),
class: "mx-auto btn btn-primary"
) %>
</div>
<%= error_tag(f, :visibility) %>
<div>
<%= submit("Save", phx_disable_with: "Saving...") %>
</div>
</.form>
</div>

View File

@ -1,89 +1,46 @@
defmodule MemexWeb.PipelineLive.Index do
use MemexWeb, :live_view
alias Memex.{Accounts.User, Pipelines, Pipelines.Pipeline}
alias Memex.Pipelines
alias Memex.Pipelines.Pipeline
@impl true
def mount(%{"search" => search}, _session, socket) do
{:ok, socket |> assign(search: search) |> display_pipelines()}
end
def mount(_params, _session, socket) do
{:ok, socket |> assign(search: nil) |> display_pipelines()}
{:ok, assign(socket, :pipelines, list_pipelines())}
end
@impl true
def handle_params(params, _url, %{assigns: %{live_action: live_action}} = socket) do
{:noreply, apply_action(socket, live_action, params)}
def handle_params(params, _url, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end
defp apply_action(%{assigns: %{current_user: current_user}} = socket, :edit, %{"slug" => slug}) do
%{slug: slug} = pipeline = Pipelines.get_pipeline_by_slug(slug, current_user)
defp apply_action(socket, :edit, %{"id" => id}) do
socket
|> assign(page_title: gettext("edit %{slug}", slug: slug))
|> assign(pipeline: pipeline)
|> assign(:page_title, "edit pipeline")
|> assign(:pipeline, Pipelines.get_pipeline!(id))
end
defp apply_action(%{assigns: %{current_user: %{id: current_user_id}}} = socket, :new, _params) do
defp apply_action(socket, :new, _params) do
socket
|> assign(page_title: gettext("new pipeline"))
|> assign(pipeline: %Pipeline{visibility: :private, user_id: current_user_id})
|> assign(:page_title, "new Pipeline")
|> assign(:pipeline, %Pipeline{})
end
defp apply_action(socket, :index, _params) do
socket
|> assign(page_title: gettext("pipelines"))
|> assign(search: nil)
|> assign(pipeline: nil)
|> display_pipelines()
end
defp apply_action(socket, :search, %{"search" => search}) do
socket
|> assign(page_title: gettext("pipelines"))
|> assign(search: search)
|> assign(pipeline: nil)
|> display_pipelines()
|> assign(:page_title, "listing pipelines")
|> assign(:pipeline, nil)
end
@impl true
def handle_event("delete", %{"id" => id}, %{assigns: %{current_user: current_user}} = socket) do
pipeline = Pipelines.get_pipeline!(id, current_user)
{:ok, %{slug: slug}} = Pipelines.delete_pipeline(pipeline, current_user)
def handle_event("delete", %{"id" => id}, socket) do
pipeline = Pipelines.get_pipeline!(id)
{:ok, _} = Pipelines.delete_pipeline(pipeline)
socket =
socket
|> assign(pipelines: Pipelines.list_pipelines(current_user))
|> put_flash(:info, gettext("%{slug} deleted", slug: slug))
{:noreply, socket}
{:noreply, assign(socket, :pipelines, list_pipelines())}
end
@impl true
def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do
{:noreply, socket |> push_patch(to: Routes.pipeline_index_path(Endpoint, :index))}
defp list_pipelines do
Pipelines.list_pipelines()
end
def handle_event("search", %{"search" => %{"search_term" => search_term}}, socket) do
{:noreply,
socket |> push_patch(to: Routes.pipeline_index_path(Endpoint, :search, search_term))}
end
defp display_pipelines(%{assigns: %{current_user: current_user, search: search}} = socket)
when not (current_user |> is_nil()) do
socket |> assign(pipelines: Pipelines.list_pipelines(search, current_user))
end
defp display_pipelines(%{assigns: %{search: search}} = socket) do
socket |> assign(pipelines: Pipelines.list_public_pipelines(search))
end
@spec is_owner_or_admin?(Pipeline.t(), User.t()) :: boolean()
defp is_owner_or_admin?(%{user_id: user_id}, %{id: user_id}), do: true
defp is_owner_or_admin?(_context, %{role: :admin}), do: true
defp is_owner_or_admin?(_context, _other_user), do: false
@spec is_owner?(Pipeline.t(), User.t()) :: boolean()
defp is_owner?(%{user_id: user_id}, %{id: user_id}), do: true
defp is_owner?(_context, _other_user), do: false
end

View File

@ -1,71 +1,10 @@
<div class="mx-auto flex flex-col justify-center items-start space-y-4 max-w-3xl">
<h1 class="text-xl">
<%= gettext("pipelines") %>
</h1>
<.form
:let={f}
for={:search}
phx-change="search"
phx-submit="search"
class="self-stretch flex flex-col items-stretch"
>
<%= text_input(f, :search_term,
class: "input input-primary",
value: @search,
phx_debounce: 300,
placeholder: gettext("search")
) %>
</.form>
<%= if @pipelines |> Enum.empty?() do %>
<h1 class="self-center text-primary-500">
<%= gettext("no pipelines found") %>
</h1>
<% else %>
<.live_component
module={MemexWeb.Components.PipelinesTableComponent}
id="pipelines-index-table"
current_user={@current_user}
pipelines={@pipelines}
>
<:actions :let={pipeline}>
<%= if is_owner?(pipeline, @current_user) do %>
<.link
patch={Routes.pipeline_index_path(@socket, :edit, pipeline.slug)}
data-qa={"pipeline-edit-#{pipeline.id}"}
>
<%= dgettext("actions", "edit") %>
</.link>
<% end %>
<%= if is_owner_or_admin?(pipeline, @current_user) do %>
<.link
href="#"
phx-click="delete"
phx-value-id={pipeline.id}
data-confirm={dgettext("prompts", "are you sure?")}
data-qa={"delete-pipeline-#{pipeline.id}"}
>
<%= dgettext("actions", "delete") %>
</.link>
<% end %>
</:actions>
</.live_component>
<% end %>
<%= if @current_user do %>
<.link patch={Routes.pipeline_index_path(@socket, :new)} class="self-end btn btn-primary">
<%= dgettext("actions", "new pipeline") %>
</.link>
<% end %>
</div>
<h1>listing pipelines</h1>
<%= if @live_action in [:new, :edit] do %>
<.modal return_to={Routes.pipeline_index_path(@socket, :index)}>
<.live_component
module={MemexWeb.PipelineLive.FormComponent}
id={@pipeline.id || :new}
current_user={@current_user}
title={@page_title}
action={@live_action}
pipeline={@pipeline}
@ -73,3 +12,53 @@
/>
</.modal>
<% end %>
<table>
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Visibility</th>
<th></th>
</tr>
</thead>
<tbody id="pipelines">
<%= for pipeline <- @pipelines do %>
<tr id={"pipeline-#{pipeline.id}"}>
<td><%= pipeline.title %></td>
<td><%= pipeline.description %></td>
<td><%= pipeline.visibility %></td>
<td>
<span>
<.link navigate={Routes.pipeline_show_path(@socket, :show, pipeline)}>
<%= dgettext("actions", "show") %>
</.link>
</span>
<span>
<.link patch={Routes.pipeline_index_path(@socket, :edit, pipeline)}>
<%= dgettext("actions", "edit") %>
</.link>
</span>
<span>
<.link
href="#"
phx-click="delete"
phx-value-id={pipeline.id}
data-confirm={dgettext("prompts", "are you sure?")}
>
<%= dgettext("actions", "delete") %>
</.link>
</span>
</td>
</tr>
<% end %>
</tbody>
</table>
<span>
<.link patch={Routes.pipeline_index_path(@socket, :new)}>
<%= dgettext("actions", "new pipeline") %>
</.link>
</span>

View File

@ -1,8 +1,7 @@
defmodule MemexWeb.PipelineLive.Show do
use MemexWeb, :live_view
import MemexWeb.Components.StepContent
alias Memex.{Accounts.User, Pipelines}
alias Memex.Pipelines.{Pipeline, Steps, Steps.Step}
alias Memex.Pipelines
@impl true
def mount(_params, _session, socket) do
@ -10,128 +9,13 @@ defmodule MemexWeb.PipelineLive.Show do
end
@impl true
def handle_params(
%{"slug" => slug} = params,
_url,
%{assigns: %{current_user: current_user, live_action: live_action}} = socket
) do
pipeline =
case Pipelines.get_pipeline_by_slug(slug, current_user) do
nil -> raise MemexWeb.NotFoundError, gettext("%{slug} could not be found", slug: slug)
pipeline -> pipeline
end
socket =
def handle_params(%{"id" => id}, _, socket) do
{:noreply,
socket
|> assign(:page_title, page_title(live_action, pipeline))
|> assign(:pipeline, pipeline)
|> assign(:steps, pipeline |> Steps.list_steps(current_user))
|> apply_action(live_action, params)
{:noreply, socket}
|> assign(:page_title, page_title(socket.assigns.live_action))
|> assign(:pipeline, Pipelines.get_pipeline!(id))}
end
defp apply_action(socket, live_action, _params) when live_action in [:show, :edit] do
socket
end
defp apply_action(
%{
assigns: %{
steps: steps,
pipeline: %{id: pipeline_id},
current_user: %{id: current_user_id}
}
} = socket,
:add_step,
_params
) do
socket
|> assign(
step: %Step{
position: steps |> Enum.count(),
pipeline_id: pipeline_id,
user_id: current_user_id
}
)
end
defp apply_action(
%{assigns: %{current_user: current_user}} = socket,
:edit_step,
%{"step_id" => step_id}
) do
socket |> assign(step: step_id |> Steps.get_step!(current_user))
end
@impl true
def handle_event(
"delete",
_params,
%{assigns: %{pipeline: pipeline, current_user: current_user}} = socket
) do
{:ok, %{slug: slug}} = Pipelines.delete_pipeline(pipeline, current_user)
socket =
socket
|> put_flash(:info, gettext("%{slug} deleted", slug: slug))
|> push_navigate(to: Routes.pipeline_index_path(Endpoint, :index))
{:noreply, socket}
end
@impl true
def handle_event(
"delete_step",
%{"step-id" => step_id},
%{assigns: %{pipeline: %{slug: pipeline_slug}, current_user: current_user}} = socket
) do
{:ok, %{title: title}} =
step_id
|> Steps.get_step!(current_user)
|> Steps.delete_step(current_user)
socket =
socket
|> put_flash(:info, gettext("%{title} deleted", title: title))
|> push_patch(to: Routes.pipeline_show_path(Endpoint, :show, pipeline_slug))
{:noreply, socket}
end
@impl true
def handle_event(
"reorder_step",
%{"step-id" => step_id, "direction" => direction},
%{assigns: %{pipeline: %{slug: pipeline_slug}, current_user: current_user}} = socket
) do
direction = if direction == "up", do: :up, else: :down
{:ok, _step} =
step_id
|> Steps.get_step!(current_user)
|> Steps.reorder_step(direction, current_user)
socket =
socket
|> push_patch(to: Routes.pipeline_show_path(Endpoint, :show, pipeline_slug))
{:noreply, socket}
end
defp page_title(:show, %{slug: slug}), do: slug
defp page_title(live_action, %{slug: slug}) when live_action in [:edit, :edit_step],
do: gettext("edit %{slug}", slug: slug)
defp page_title(:add_step, %{slug: slug}), do: gettext("add step to %{slug}", slug: slug)
@spec is_owner_or_admin?(Pipeline.t(), User.t()) :: boolean()
defp is_owner_or_admin?(%{user_id: user_id}, %{id: user_id}), do: true
defp is_owner_or_admin?(_context, %{role: :admin}), do: true
defp is_owner_or_admin?(_context, _other_user), do: false
@spec is_owner?(Pipeline.t(), User.t()) :: boolean()
defp is_owner?(%{user_id: user_id}, %{id: user_id}), do: true
defp is_owner?(_context, _other_user), do: false
defp page_title(:show), do: "show pipeline"
defp page_title(:edit), do: "edit pipeline"
end

View File

@ -1,180 +1,43 @@
<div class="mx-auto flex flex-col justify-center items-stretch space-y-4 max-w-3xl">
<h1 class="text-xl">
<%= @pipeline.slug %>
</h1>
<h1>show pipeline</h1>
<div class="flex flex-wrap space-x-1">
<%= for tag <- @pipeline.tags do %>
<.link navigate={Routes.pipeline_index_path(Endpoint, :search, tag)} class="link">
<%= tag %>
</.link>
<% end %>
</div>
<%= if @pipeline.description do %>
<textarea
id="show-pipeline-description"
class="input input-primary h-32 min-h-32"
phx-hook="MaintainAttrs"
phx-update="ignore"
readonly
phx-no-format
><%= @pipeline.description %></textarea>
<% end %>
<p class="self-end">
<%= gettext("Visibility: %{visibility}", visibility: @pipeline.visibility) %>
</p>
<div class="pb-4 self-end flex space-x-4">
<.link class="btn btn-primary" navigate={Routes.pipeline_index_path(@socket, :index)}>
<%= dgettext("actions", "back") %>
</.link>
<%= if is_owner?(@pipeline, @current_user) do %>
<.link
class="btn btn-primary"
patch={Routes.pipeline_show_path(@socket, :edit, @pipeline.slug)}
>
<%= dgettext("actions", "edit") %>
</.link>
<% end %>
<%= if is_owner_or_admin?(@pipeline, @current_user) do %>
<button
type="button"
class="btn btn-primary"
phx-click="delete"
data-confirm={dgettext("prompts", "are you sure?")}
data-qa={"delete-pipeline-#{@pipeline.id}"}
>
<%= dgettext("actions", "delete") %>
</button>
<% end %>
</div>
<hr class="hr" />
<h2 class="pt-2 self-center text-lg">
<%= gettext("steps:") %>
</h2>
<%= if @steps |> Enum.empty?() do %>
<h3 class="self-center text-md text-primary-600">
<%= gettext("no steps") %>
</h3>
<% else %>
<%= for %{id: step_id, position: position, title: title} = step <- @steps do %>
<div class="flex justify-between items-center space-x-4">
<h3 class="text-md">
<%= gettext("%{position}. %{title}", position: position + 1, title: title) %>
</h3>
<%= if is_owner?(@pipeline, @current_user) do %>
<div class="flex justify-between items-center space-x-4">
<%= if position <= 0 do %>
<i class="fas text-xl fa-chevron-up cursor-not-allowed opacity-25"></i>
<% else %>
<button
type="button"
class="cursor-pointer flex justify-center items-center"
phx-click="reorder_step"
phx-value-direction="up"
phx-value-step-id={step_id}
data-qa={"move-step-up-#{step_id}"}
>
<i class="fas text-xl fa-chevron-up"></i>
</button>
<% end %>
<%= if position >= length(@steps) - 1 do %>
<i class="fas text-xl fa-chevron-down cursor-not-allowed opacity-25"></i>
<% else %>
<button
type="button"
class="cursor-pointer flex justify-center items-center"
phx-click="reorder_step"
phx-value-direction="down"
phx-value-step-id={step_id}
data-qa={"move-step-down-#{step_id}"}
>
<i class="fas text-xl fa-chevron-down"></i>
</button>
<% end %>
<.link
class="self-end btn btn-primary"
patch={Routes.pipeline_show_path(@socket, :edit_step, @pipeline.slug, step_id)}
data-qa={"edit-step-#{step_id}"}
>
<%= dgettext("actions", "edit") %>
</.link>
<button
type="button"
class="btn btn-primary"
phx-click="delete_step"
phx-value-step-id={step_id}
data-confirm={dgettext("prompts", "are you sure?")}
data-qa={"delete-step-#{step_id}"}
>
<%= dgettext("actions", "delete") %>
</button>
</div>
<% end %>
</div>
<.step_content step={step} />
<% end %>
<% end %>
<%= if is_owner?(@pipeline, @current_user) do %>
<.link
class="self-end btn btn-primary"
patch={Routes.pipeline_show_path(@socket, :add_step, @pipeline.slug)}
data-qa={"add-step-#{@pipeline.id}"}
>
<%= dgettext("actions", "add step") %>
</.link>
<% end %>
</div>
<%= case @live_action do %>
<% :edit -> %>
<.modal return_to={Routes.pipeline_show_path(@socket, :show, @pipeline.slug)}>
<%= if @live_action in [:edit] do %>
<.modal return_to={Routes.pipeline_show_path(@socket, :show, @pipeline)}>
<.live_component
module={MemexWeb.PipelineLive.FormComponent}
id={@pipeline.id}
current_user={@current_user}
title={@page_title}
action={@live_action}
pipeline={@pipeline}
return_to={Routes.pipeline_show_path(@socket, :show, @pipeline.slug)}
return_to={Routes.pipeline_show_path(@socket, :show, @pipeline)}
/>
</.modal>
<% :add_step -> %>
<.modal return_to={Routes.pipeline_show_path(@socket, :show, @pipeline.slug)}>
<.live_component
module={MemexWeb.StepLive.FormComponent}
id={@pipeline.id || :new}
current_user={@current_user}
title={@page_title}
action={@live_action}
pipeline={@pipeline}
step={@step}
return_to={Routes.pipeline_show_path(@socket, :show, @pipeline.slug)}
/>
</.modal>
<% :edit_step -> %>
<.modal return_to={Routes.pipeline_show_path(@socket, :show, @pipeline.slug)}>
<.live_component
module={MemexWeb.StepLive.FormComponent}
id={@pipeline.id || :new}
current_user={@current_user}
title={@page_title}
action={@live_action}
pipeline={@pipeline}
step={@step}
return_to={Routes.pipeline_show_path(@socket, :show, @pipeline.slug)}
/>
</.modal>
<% _ -> %>
<% end %>
<ul>
<li>
<strong>Title:</strong>
<%= @pipeline.title %>
</li>
<li>
<strong>Description:</strong>
<%= @pipeline.description %>
</li>
<li>
<strong>Visibility:</strong>
<%= @pipeline.visibility %>
</li>
</ul>
<span>
<.link patch={Routes.pipeline_show_path(@socket, :edit, @pipeline)} class="button">
<%= dgettext("actions", "edit") %>
</.link>
</span>
|
<span>
<.link patch={Routes.pipeline_index_path(@socket, :index)}>
<%= dgettext("actions", "Back") %>
</.link>
</span>

View File

@ -1,74 +0,0 @@
defmodule MemexWeb.StepLive.FormComponent do
use MemexWeb, :live_component
alias Memex.Pipelines.Steps
@impl true
def update(%{step: step, current_user: current_user, pipeline: _pipeline} = assigns, socket) do
changeset = Steps.change_step(step, current_user)
{:ok,
socket
|> assign(assigns)
|> assign(:changeset, changeset)}
end
@impl true
def handle_event(
"validate",
%{"step" => step_params},
%{assigns: %{step: step, current_user: current_user}} = socket
) do
changeset =
step
|> Steps.change_step(step_params, current_user)
|> Map.put(:action, :validate)
{:noreply, assign(socket, :changeset, changeset)}
end
def handle_event("save", %{"step" => step_params}, %{assigns: %{action: action}} = socket) do
save_step(socket, action, step_params)
end
defp save_step(
%{assigns: %{step: step, return_to: return_to, current_user: current_user}} = socket,
:edit_step,
step_params
) do
case Steps.update_step(step, step_params, current_user) do
{:ok, %{title: title}} ->
{:noreply,
socket
|> put_flash(:info, gettext("%{title} saved", title: title))
|> push_navigate(to: return_to)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, :changeset, changeset)}
end
end
defp save_step(
%{
assigns: %{
step: %{position: position},
return_to: return_to,
current_user: current_user,
pipeline: pipeline
}
} = socket,
:add_step,
step_params
) do
case Steps.create_step(step_params, position, pipeline, current_user) do
{:ok, %{title: title}} ->
{:noreply,
socket
|> put_flash(:info, gettext("%{title} created", title: title))
|> push_navigate(to: return_to)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, changeset: changeset)}
end
end
end

View File

@ -1,34 +0,0 @@
<div class="h-full flex flex-col justify-start items-stretch space-y-4">
<.form
:let={f}
for={@changeset}
id="step-form"
phx-target={@myself}
phx-change="validate"
phx-submit="save"
phx-debounce="300"
class="flex flex-col justify-start items-stretch space-y-4"
>
<%= text_input(f, :title,
class: "input input-primary",
placeholder: gettext("title")
) %>
<%= error_tag(f, :title) %>
<%= textarea(f, :content,
id: "step-form-content",
class: "input input-primary h-64 min-h-64",
phx_hook: "MaintainAttrs",
phx_update: "ignore",
placeholder: gettext("use [[context-slug]] to link to a context")
) %>
<%= error_tag(f, :content) %>
<div class="flex justify-center items-stretch space-x-4">
<%= submit(dgettext("actions", "save"),
phx_disable_with: gettext("saving..."),
class: "mx-auto btn btn-primary"
) %>
</div>
</.form>
</div>

View File

@ -1,3 +0,0 @@
defmodule MemexWeb.NotFoundError do
defexception [:message, plug_status: 404]
end

View File

@ -36,7 +36,6 @@ defmodule MemexWeb.Router do
pipe_through :browser
live "/", HomeLive
live "/faq", FaqLive
end
## Authentication routes
@ -59,18 +58,16 @@ defmodule MemexWeb.Router do
pipe_through [:browser, :require_authenticated_user]
live "/notes/new", NoteLive.Index, :new
live "/notes/:slug/edit", NoteLive.Index, :edit
live "/note/:slug/edit", NoteLive.Show, :edit
live "/notes/:id/edit", NoteLive.Index, :edit
live "/note/:id/edit", NoteLive.Show, :edit
live "/contexts/new", ContextLive.Index, :new
live "/contexts/:slug/edit", ContextLive.Index, :edit
live "/context/:slug/edit", ContextLive.Show, :edit
live "/contexts/:id/edit", ContextLive.Index, :edit
live "/context/:id/show/edit", ContextLive.Show, :edit
live "/pipelines/new", PipelineLive.Index, :new
live "/pipelines/:slug/edit", PipelineLive.Index, :edit
live "/pipeline/:slug/edit", PipelineLive.Show, :edit
live "/pipeline/:slug/add_step", PipelineLive.Show, :add_step
live "/pipeline/:slug/:step_id", PipelineLive.Show, :edit_step
live "/pipelines/:id/edit", PipelineLive.Index, :edit
live "/pipeline/:id/edit", PipelineLive.Show, :edit
get "/users/settings", UserSettingsController, :edit
put "/users/settings", UserSettingsController, :update
@ -83,15 +80,13 @@ defmodule MemexWeb.Router do
live "/notes", NoteLive.Index, :index
live "/notes/:search", NoteLive.Index, :search
live "/note/:slug", NoteLive.Show, :show
live "/note/:id", NoteLive.Show, :show
live "/contexts", ContextLive.Index, :index
live "/contexts/:search", ContextLive.Index, :search
live "/context/:slug", ContextLive.Show, :show
live "/context/:id", ContextLive.Show, :show
live "/pipelines", PipelineLive.Index, :index
live "/pipelines/:search", PipelineLive.Index, :search
live "/pipeline/:slug", PipelineLive.Show, :show
live "/pipeline/:id", PipelineLive.Show, :show
end
end

View File

@ -6,7 +6,7 @@
<br />
<span style="margin-bottom: 1em; font-size: 1.25em;">
<%= dgettext("emails", "Welcome to memEx") %>
<%= dgettext("emails", "Welcome to Memex") %>
</span>
<br />
@ -19,5 +19,5 @@
<br />
<%= dgettext("emails", "If you didn't create an account at memEx, please ignore this.") %>
<%= dgettext("emails", "If you didn't create an account at Memex, please ignore this.") %>
</div>

View File

@ -1,7 +1,7 @@
<%= dgettext("emails", "Hi %{email},", email: @user.email) %>
<%= dgettext("emails", "Welcome to memEx") %>
<%= dgettext("emails", "Welcome to Memex") %>
<%= dgettext("emails", "You can confirm your account by visiting the URL below:") %>

View File

@ -13,5 +13,5 @@
<br />
<%= dgettext("emails", "If you didn't request this change from memEx, please ignore this.") %>
<%= dgettext("emails", "If you didn't request this change from Memex, please ignore this.") %>
</div>

View File

@ -15,6 +15,6 @@
<%= dgettext(
"emails",
"If you didn't request this change from memEx, please ignore this."
"If you didn't request this change from Memex, please ignore this."
) %>
</div>

View File

@ -5,13 +5,13 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>
<%= dgettext("errors", "Error") %> | memEx
<%= dgettext("errors", "Error") %>| Memex
</title>
<link rel="stylesheet" href="/css/app.css" />
<script defer type="text/javascript" src="/js/app.js">
</script>
</head>
<body class="m-0 p-0 w-full h-full bg-primary-800 text-primary-400 subpixel-antialiased">
<body class="pb-8 m-0 p-0 w-full h-full">
<header>
<.topbar current_user={assigns[:current_user]}></.topbar>
</header>
@ -25,7 +25,7 @@
<hr class="w-full hr" />
<a href={Routes.live_path(Endpoint, HomeLive)} class="link title text-primary-400 text-lg">
<%= dgettext("errors", "go back home") %>
<%= dgettext("errors", "Go back home") %>
</a>
</div>
</div>

View File

@ -4,15 +4,15 @@
<%= @email.subject %>
</title>
</head>
<body style="padding: 2em; color: rgb(161, 161, 170); background-color: rgb(39, 39, 42); font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; text-align: center;">
<body style="padding: 2em; color: rgb(31, 31, 31); background-color: rgb(220, 220, 228); font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; text-align: center;">
<%= @inner_content %>
<hr style="margin: 2em auto; border-width: 1px; border-color: rgb(161, 161, 170); width: 100%; max-width: 42rem;" />
<hr style="margin: 2em auto; border-width: 1px; border-color: rgb(212, 212, 216); width: 100%; max-width: 42rem;" />
<a style="color: rgb(161, 161, 170);" href={Routes.live_url(Endpoint, HomeLive)}>
<a style="color: rgb(31, 31, 31);" href={Routes.live_url(Endpoint, HomeLive)}>
<%= dgettext(
"emails",
"This email was sent from memEx"
"This email was sent from Memex, the self-hosted firearm tracker website."
) %>
</a>
</body>

View File

@ -7,5 +7,5 @@
=====================
<%= dgettext("emails",
"This email was sent from memEx at %{url}",
"This email was sent from Memex at %{url}, the self-hosted firearm tracker website.",
url: Routes.live_url(Endpoint, HomeLive)) %>

View File

@ -5,8 +5,8 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<%= csrf_meta_tag() %>
<.live_title suffix={" | #{gettext("memEx")}"}>
<%= assigns[:page_title] || gettext("memEx") %>
<.live_title suffix={" | #{gettext("memex")}"}>
<%= assigns[:page_title] || gettext("memex") %>
</.live_title>
<link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/css/app.css")} />
<script

View File

@ -33,7 +33,7 @@
<%= select(
f,
:locale,
[{gettext("english"), "en_US"}],
[{gettext("English"), "en_US"}],
class: "input input-primary col-span-2"
) %>
<%= error_tag(f, :locale) %>
@ -48,7 +48,7 @@
<%= dgettext("actions", "log in") %>
</.link>
<.link href={Routes.user_reset_password_path(@conn, :new)} class="btn btn-primary">
<%= dgettext("actions", "forgot your password?") %>
<%= dgettext("actions", "Forgot your password?") %>
</.link>
</div>
</div>

View File

@ -1,6 +1,6 @@
<div class="mx-auto mb-8 max-w-2xl flex flex-col justify-center items-center space-y-4">
<h1 class="title text-primary-400 text-xl">
<%= dgettext("actions", "forgot your password?") %>
<%= dgettext("actions", "Forgot your password?") %>
</h1>
<.form
@ -12,7 +12,7 @@
<%= label(f, :email, class: "title text-lg text-primary-400") %>
<%= email_input(f, :email, required: true, class: "input input-primary col-span-2") %>
<%= submit(dgettext("actions", "send instructions to reset password"),
<%= submit(dgettext("actions", "Send instructions to reset password"),
class: "mx-auto btn btn-primary col-span-3"
) %>
</.form>

View File

@ -41,7 +41,7 @@
</.link>
<% end %>
<.link href={Routes.user_reset_password_path(@conn, :new)} class="btn btn-primary">
<%= dgettext("actions", "forgot your password?") %>
<%= dgettext("actions", "Forgot your password?") %>
</.link>
</div>
</div>

View File

@ -6,9 +6,9 @@ defmodule MemexWeb.ErrorView do
def template_not_found(error_path, _assigns) do
error_string =
case error_path do
"404.html" -> dgettext("errors", "not found")
"401.html" -> dgettext("errors", "unauthorized")
_ -> dgettext("errors", "internal server error")
"404.html" -> dgettext("errors", "Not found")
"401.html" -> dgettext("errors", "Unauthorized")
_ -> dgettext("errors", "Internal Server Error")
end
render("error.html", %{error_string: error_string})

View File

@ -1,12 +1,17 @@
defmodule MemexWeb.LayoutView do
use MemexWeb, :view
import MemexWeb.{Components.Topbar, Gettext}
import MemexWeb.Components.Topbar
alias MemexWeb.HomeLive
# Phoenix LiveDashboard is available only in development by default,
# so we instruct Elixir to not warn if the dashboard route is missing.
@compile {:no_warn_undefined, {Routes, :live_dashboard_path, 2}}
def get_title(%{assigns: %{title: title}}), do: gettext("memEx | %{title}", title: title)
def get_title(_conn), do: gettext("memEx")
def get_title(conn) do
if conn.assigns |> Map.has_key?(:title) do
"Memex | #{conn.assigns.title}"
else
"Memex"
end
end
end

View File

@ -4,7 +4,7 @@ defmodule Memex.MixProject do
def project do
[
app: :memex,
version: "0.1.5",
version: "0.1.0",
elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(),
@ -15,9 +15,9 @@ defmodule Memex.MixProject do
consolidate_protocols: Mix.env() not in [:dev, :test],
preferred_cli_env: [test: :test, "test.all": :test],
# ExDoc
name: "memEx",
source_url: "https://gitea.bubbletea.dev/shibao/memEx",
homepage_url: "https://gitea.bubbletea.dev/shibao/memEx",
name: "memex",
source_url: "https://gitea.bubbletea.dev/shibao/memex",
homepage_url: "https://gitea.bubbletea.dev/shibao/memex",
docs: [
# The main page in the docs
main: "README.md",

View File

@ -10,148 +10,95 @@
msgid ""
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:30
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/user_settings/edit.html.heex:113
msgid "Change Language"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/user_settings/edit.html.heex:15
#: lib/memex_web/templates/user_settings/edit.html.heex:44
msgid "Change email"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/user_settings/edit.html.heex:131
msgid "Change language"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/user_settings/edit.html.heex:58
#: lib/memex_web/templates/user_settings/edit.html.heex:99
msgid "Change password"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/index.html.heex:32
msgid "Copy to clipboard"
msgstr ""
#: lib/memex_web/templates/user_confirmation/new.html.heex:3
#: lib/memex_web/templates/user_confirmation/new.html.heex:15
#, elixir-autogen, elixir-format
msgid "Resend confirmation instructions"
msgstr ""
#: lib/memex_web/templates/user_reset_password/edit.html.heex:3
#: lib/memex_web/templates/user_reset_password/edit.html.heex:33
#, elixir-autogen, elixir-format
msgid "Reset password"
msgstr ""
#: lib/memex_web/live/invite_live/form_component.html.heex:28
#, elixir-autogen, elixir-format
msgid "Save"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:15
#: lib/memex_web/templates/user_settings/edit.html.heex:44
#, elixir-autogen, elixir-format
msgid "change email"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:113
#: lib/memex_web/templates/user_settings/edit.html.heex:131
#, elixir-autogen, elixir-format
msgid "change language"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:58
#: lib/memex_web/templates/user_settings/edit.html.heex:99
#, elixir-autogen, elixir-format
msgid "change password"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:16
#, elixir-autogen, elixir-format
msgid "create invite"
msgid "Create Invite"
msgstr ""
#: lib/memex_web/live/context_live/index.html.heex:49
#: lib/memex_web/live/context_live/show.html.heex:40
#: lib/memex_web/live/note_live/index.html.heex:49
#: lib/memex_web/live/note_live/show.html.heex:37
#: lib/memex_web/live/pipeline_live/index.html.heex:49
#: lib/memex_web/live/pipeline_live/show.html.heex:49
#: lib/memex_web/live/pipeline_live/show.html.heex:119
#, elixir-autogen, elixir-format
msgid "delete"
#: lib/memex_web/templates/user_settings/edit.html.heex:139
msgid "Delete User"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:145
#, elixir-autogen, elixir-format
msgid "delete user"
#: lib/memex_web/templates/user_registration/new.html.heex:51
#: lib/memex_web/templates/user_reset_password/new.html.heex:3
#: lib/memex_web/templates/user_session/new.html.heex:44
msgid "Forgot your password?"
msgstr ""
#: lib/memex_web/live/context_live/index.html.heex:38
#: lib/memex_web/live/context_live/show.html.heex:29
#: lib/memex_web/live/note_live/index.html.heex:38
#: lib/memex_web/live/note_live/show.html.heex:26
#: lib/memex_web/live/pipeline_live/index.html.heex:38
#: lib/memex_web/live/pipeline_live/show.html.heex:38
#: lib/memex_web/live/pipeline_live/show.html.heex:108
#, elixir-autogen, elixir-format
msgid "edit"
#: lib/memex_web/live/invite_live/index.html.heex:11
msgid "Invite someone new!"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:12
#, elixir-autogen, elixir-format
msgid "invite someone new!"
msgstr ""
#: lib/memex_web/components/topbar.ex:125
#: lib/memex_web/components/topbar.ex:119
#: lib/memex_web/templates/user_confirmation/new.html.heex:29
#: lib/memex_web/templates/user_registration/new.html.heex:48
#: lib/memex_web/templates/user_registration/new.html.heex:47
#: lib/memex_web/templates/user_reset_password/edit.html.heex:47
#: lib/memex_web/templates/user_reset_password/new.html.heex:29
#: lib/memex_web/templates/user_session/new.html.heex:3
#: lib/memex_web/templates/user_session/new.html.heex:32
#, elixir-autogen, elixir-format
msgid "log in"
msgid "Log in"
msgstr ""
#: lib/memex_web/live/context_live/index.html.heex:58
#, elixir-autogen, elixir-format
msgid "new context"
msgstr ""
#: lib/memex_web/live/note_live/index.html.heex:58
#, elixir-autogen, elixir-format
msgid "new note"
msgstr ""
#: lib/memex_web/live/pipeline_live/index.html.heex:58
#, elixir-autogen, elixir-format
msgid "new pipeline"
msgstr ""
#: lib/memex_web/components/topbar.ex:115
#: lib/memex_web/templates/user_confirmation/new.html.heex:25
#: lib/memex_web/components/topbar.ex:111
#: lib/memex_web/templates/user_confirmation/new.html.heex:24
#: lib/memex_web/templates/user_registration/new.html.heex:3
#: lib/memex_web/templates/user_registration/new.html.heex:41
#: lib/memex_web/templates/user_reset_password/edit.html.heex:43
#: lib/memex_web/templates/user_reset_password/new.html.heex:25
#: lib/memex_web/templates/user_session/new.html.heex:40
#, elixir-autogen, elixir-format
msgid "register"
#: lib/memex_web/templates/user_reset_password/edit.html.heex:42
#: lib/memex_web/templates/user_reset_password/new.html.heex:24
#: lib/memex_web/templates/user_session/new.html.heex:39
msgid "Register"
msgstr ""
#: lib/memex_web/live/context_live/form_component.html.heex:40
#: lib/memex_web/live/note_live/form_component.html.heex:40
#: lib/memex_web/live/pipeline_live/form_component.html.heex:40
#: lib/memex_web/live/step_live/form_component.html.heex:28
#, elixir-autogen, elixir-format
msgid "save"
#: lib/memex_web/templates/user_confirmation/new.html.heex:3
#: lib/memex_web/templates/user_confirmation/new.html.heex:15
msgid "Resend confirmation instructions"
msgstr ""
#: lib/memex_web/live/context_live/show.html.heex:22
#: lib/memex_web/live/note_live/show.html.heex:22
#: lib/memex_web/live/pipeline_live/show.html.heex:31
#, elixir-autogen, elixir-format
msgid "back"
#: lib/memex_web/templates/user_reset_password/edit.html.heex:3
#: lib/memex_web/templates/user_reset_password/edit.html.heex:33
msgid "Reset password"
msgstr ""
#: lib/memex_web/live/pipeline_live/show.html.heex:135
#, elixir-autogen, elixir-format
msgid "add step"
#: lib/memex_web/live/invite_live/form_component.html.heex:28
msgid "Save"
msgstr ""
#: lib/memex_web/templates/user_registration/new.html.heex:51
#: lib/memex_web/templates/user_reset_password/new.html.heex:3
#: lib/memex_web/templates/user_session/new.html.heex:44
#, elixir-autogen, elixir-format
msgid "forgot your password?"
msgstr ""
#: lib/memex_web/templates/user_reset_password/new.html.heex:15
#, elixir-autogen, elixir-format
msgid "send instructions to reset password"
msgid "Send instructions to reset password"
msgstr ""

View File

@ -1,158 +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: de\n"
"Plural-Forms: nplurals=2\n"
#: lib/memex_web/live/invite_live/index.html.heex:30
#, elixir-autogen, elixir-format
msgid "Copy to clipboard"
msgstr ""
#: lib/memex_web/templates/user_confirmation/new.html.heex:3
#: lib/memex_web/templates/user_confirmation/new.html.heex:15
#, elixir-autogen, elixir-format
msgid "Resend confirmation instructions"
msgstr ""
#: lib/memex_web/templates/user_reset_password/edit.html.heex:3
#: lib/memex_web/templates/user_reset_password/edit.html.heex:33
#, elixir-autogen, elixir-format
msgid "Reset password"
msgstr ""
#: lib/memex_web/live/invite_live/form_component.html.heex:28
#, elixir-autogen, elixir-format
msgid "Save"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:15
#: lib/memex_web/templates/user_settings/edit.html.heex:44
#, elixir-autogen, elixir-format
msgid "change email"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:113
#: lib/memex_web/templates/user_settings/edit.html.heex:131
#, elixir-autogen, elixir-format
msgid "change language"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:58
#: lib/memex_web/templates/user_settings/edit.html.heex:99
#, elixir-autogen, elixir-format
msgid "change password"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:16
#, elixir-autogen, elixir-format
msgid "create invite"
msgstr ""
#: lib/memex_web/live/context_live/index.html.heex:49
#: lib/memex_web/live/context_live/show.html.heex:40
#: lib/memex_web/live/note_live/index.html.heex:49
#: lib/memex_web/live/note_live/show.html.heex:37
#: lib/memex_web/live/pipeline_live/index.html.heex:49
#: lib/memex_web/live/pipeline_live/show.html.heex:49
#: lib/memex_web/live/pipeline_live/show.html.heex:119
#, elixir-autogen, elixir-format
msgid "delete"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:145
#, elixir-autogen, elixir-format
msgid "delete user"
msgstr ""
#: lib/memex_web/live/context_live/index.html.heex:38
#: lib/memex_web/live/context_live/show.html.heex:29
#: lib/memex_web/live/note_live/index.html.heex:38
#: lib/memex_web/live/note_live/show.html.heex:26
#: lib/memex_web/live/pipeline_live/index.html.heex:38
#: lib/memex_web/live/pipeline_live/show.html.heex:38
#: lib/memex_web/live/pipeline_live/show.html.heex:108
#, elixir-autogen, elixir-format
msgid "edit"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:12
#, elixir-autogen, elixir-format
msgid "invite someone new!"
msgstr ""
#: lib/memex_web/components/topbar.ex:125
#: lib/memex_web/templates/user_confirmation/new.html.heex:29
#: lib/memex_web/templates/user_registration/new.html.heex:48
#: lib/memex_web/templates/user_reset_password/edit.html.heex:47
#: lib/memex_web/templates/user_reset_password/new.html.heex:29
#: lib/memex_web/templates/user_session/new.html.heex:3
#: lib/memex_web/templates/user_session/new.html.heex:32
#, elixir-autogen, elixir-format
msgid "log in"
msgstr ""
#: lib/memex_web/live/context_live/index.html.heex:58
#, elixir-autogen, elixir-format
msgid "new context"
msgstr ""
#: lib/memex_web/live/note_live/index.html.heex:58
#, elixir-autogen, elixir-format
msgid "new note"
msgstr ""
#: lib/memex_web/live/pipeline_live/index.html.heex:58
#, elixir-autogen, elixir-format
msgid "new pipeline"
msgstr ""
#: lib/memex_web/components/topbar.ex:115
#: lib/memex_web/templates/user_confirmation/new.html.heex:25
#: lib/memex_web/templates/user_registration/new.html.heex:3
#: lib/memex_web/templates/user_registration/new.html.heex:41
#: lib/memex_web/templates/user_reset_password/edit.html.heex:43
#: lib/memex_web/templates/user_reset_password/new.html.heex:25
#: lib/memex_web/templates/user_session/new.html.heex:40
#, elixir-autogen, elixir-format
msgid "register"
msgstr ""
#: lib/memex_web/live/context_live/form_component.html.heex:40
#: lib/memex_web/live/note_live/form_component.html.heex:40
#: lib/memex_web/live/pipeline_live/form_component.html.heex:40
#: lib/memex_web/live/step_live/form_component.html.heex:28
#, elixir-autogen, elixir-format
msgid "save"
msgstr ""
#: lib/memex_web/live/context_live/show.html.heex:22
#: lib/memex_web/live/note_live/show.html.heex:22
#: lib/memex_web/live/pipeline_live/show.html.heex:31
#, elixir-autogen, elixir-format
msgid "back"
msgstr ""
#: lib/memex_web/live/pipeline_live/show.html.heex:135
#, elixir-autogen, elixir-format
msgid "add step"
msgstr ""
#: lib/memex_web/templates/user_registration/new.html.heex:51
#: lib/memex_web/templates/user_reset_password/new.html.heex:3
#: lib/memex_web/templates/user_session/new.html.heex:44
#, elixir-autogen, elixir-format, fuzzy
msgid "forgot your password?"
msgstr ""
#: lib/memex_web/templates/user_reset_password/new.html.heex:15
#, elixir-autogen, elixir-format, fuzzy
msgid "send instructions to reset password"
msgstr ""

View File

@ -1,667 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-11-27 04:49+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Translate Toolkit 3.7.4\n"
## This file is a PO Template file.
##
## "msgid"s here are often extracted from source code.
## Add new translations manually only if they're dynamic
## translations 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.
#: lib/memex_web/live/invite_live/index.html.heex:90
#, elixir-autogen, elixir-format
msgid "Admins"
msgstr ""
#: lib/memex_web/controllers/user_confirmation_controller.ex:8
#, elixir-autogen, elixir-format
msgid "Confirm your account"
msgstr ""
#: lib/memex_web/templates/user_session/new.html.heex:27
#, elixir-autogen, elixir-format
msgid "Keep me logged in for 60 days"
msgstr ""
#: lib/memex_web/templates/user_registration/new.html.heex:32
#, elixir-autogen, elixir-format
msgid "Language"
msgstr ""
#: lib/memex_web/templates/layout/live.html.heex:37
#, elixir-autogen, elixir-format
msgid "Loading..."
msgstr ""
#: lib/memex_web/live/invite_live/form_component.html.heex:20
#, elixir-autogen, elixir-format
msgid "Name"
msgstr ""
#: lib/memex_web/templates/layout/live.html.heex:50
#, elixir-autogen, elixir-format
msgid "Reconnecting..."
msgstr ""
#: lib/memex_web/controllers/user_reset_password_controller.ex:36
#, elixir-autogen, elixir-format
msgid "Reset your password"
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:10
#, elixir-autogen, elixir-format
msgid "Settings"
msgstr ""
#: lib/memex_web/components/invite_card.ex:19
#, elixir-autogen, elixir-format
msgid "Uses Left:"
msgstr ""
#: lib/memex_web/live/invite_live/form_component.html.heex:24
#, elixir-autogen, elixir-format
msgid "Uses left"
msgstr ""
#: lib/memex_web/live/context_live/show.html.heex:17
#: lib/memex_web/live/note_live/show.html.heex:17
#: lib/memex_web/live/pipeline_live/show.html.heex:26
#, elixir-autogen, elixir-format
msgid "Visibility: %{visibility}"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:76
#, elixir-autogen, elixir-format
msgid "accessible from any internet-capable device"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:90
#, elixir-autogen, elixir-format
msgid "admins:"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:58
#, elixir-autogen, elixir-format
msgid "built with sharing and collaboration in mind"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:78
#, elixir-autogen, elixir-format
msgid "confirm new password"
msgstr ""
#: lib/memex_web/live/note_live/form_component.html.heex:23
#, elixir-autogen, elixir-format
msgid "content"
msgstr ""
#: lib/memex_web/components/topbar.ex:52
#: lib/memex_web/live/context_live/index.ex:35
#: lib/memex_web/live/context_live/index.ex:43
#: lib/memex_web/live/context_live/index.html.heex:3
#, elixir-autogen, elixir-format
msgid "contexts"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:20
#, elixir-autogen, elixir-format
msgid "contexts:"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:73
#, elixir-autogen, elixir-format
msgid "convenient:"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:32
#: lib/memex_web/templates/user_settings/edit.html.heex:87
#, elixir-autogen, elixir-format
msgid "current password"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:59
#, elixir-autogen, elixir-format
msgid "disable"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:14
#, elixir-autogen, elixir-format
msgid "document notes about individual items or concepts"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:32
#, elixir-autogen, elixir-format
msgid "document your processes, attaching contexts to each step"
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:33
#, elixir-autogen, elixir-format
msgid "edit invite"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:28
#, elixir-autogen, elixir-format
msgid "email"
msgstr ""
#: lib/memex_web/components/user_card.ex:23
#, elixir-autogen, elixir-format
msgid "email unconfirmed"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:63
#, elixir-autogen, elixir-format
msgid "enable"
msgstr ""
#: lib/memex_web/templates/user_registration/new.html.heex:36
#: lib/memex_web/templates/user_settings/edit.html.heex:126
#, elixir-autogen, elixir-format
msgid "english"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:50
#, elixir-autogen, elixir-format
msgid "features"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:138
#, elixir-autogen, elixir-format
msgid "get involved!"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:159
#, elixir-autogen, elixir-format
msgid "help translate"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:85
#, elixir-autogen, elixir-format
msgid "instance information"
msgstr ""
#: lib/memex_web/components/invite_card.ex:24
#, elixir-autogen, elixir-format
msgid "invite disabled"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:115
#, elixir-autogen, elixir-format
msgid "invite only"
msgstr ""
#: lib/memex_web/components/topbar.ex:74
#: lib/memex_web/live/invite_live/index.ex:41
#: lib/memex_web/live/invite_live/index.html.heex:3
#, elixir-autogen, elixir-format
msgid "invites"
msgstr ""
#: lib/memex_web/controllers/user_session_controller.ex:8
#, elixir-autogen, elixir-format
msgid "log in"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:55
#, elixir-autogen, elixir-format
msgid "multi-user:"
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:37
#, elixir-autogen, elixir-format
msgid "new invite"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:71
#, elixir-autogen, elixir-format
msgid "new password"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:8
#, elixir-autogen, elixir-format
msgid "no invites 😔"
msgstr ""
#: lib/memex_web/live/note_live/index.html.heex:23
#, elixir-autogen, elixir-format
msgid "no notes found"
msgstr ""
#: lib/memex_web/components/topbar.ex:43
#: lib/memex_web/live/note_live/index.ex:35
#: lib/memex_web/live/note_live/index.ex:43
#: lib/memex_web/live/note_live/index.html.heex:3
#, elixir-autogen, elixir-format
msgid "notes"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:11
#, elixir-autogen, elixir-format
msgid "notes:"
msgstr ""
#: lib/memex_web/components/topbar.ex:61
#: lib/memex_web/live/pipeline_live/index.ex:35
#: lib/memex_web/live/pipeline_live/index.ex:43
#: lib/memex_web/live/pipeline_live/index.html.heex:3
#, elixir-autogen, elixir-format
msgid "pipelines"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:29
#, elixir-autogen, elixir-format
msgid "pipelines:"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:67
#, elixir-autogen, elixir-format
msgid "privacy controls on a per-note, context or pipeline basis"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:64
#, elixir-autogen, elixir-format
msgid "privacy:"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:23
#, elixir-autogen, elixir-format
msgid "provide context around a single topic and hotlink to your notes"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:114
#, elixir-autogen, elixir-format
msgid "public signups"
msgstr ""
#: lib/memex_web/controllers/user_registration_controller.ex:34
#, elixir-autogen, elixir-format
msgid "register"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:110
#, elixir-autogen, elixir-format
msgid "registration:"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:170
#, elixir-autogen, elixir-format
msgid "report bugs or request features"
msgstr ""
#: lib/memex_web/live/context_live/form_component.html.heex:41
#: lib/memex_web/live/note_live/form_component.html.heex:41
#: lib/memex_web/live/pipeline_live/form_component.html.heex:41
#: lib/memex_web/live/step_live/form_component.html.heex:29
#, elixir-autogen, elixir-format
msgid "saving..."
msgstr ""
#: lib/memex_web/live/context_live/form_component.html.heex:37
#: lib/memex_web/live/note_live/form_component.html.heex:37
#: lib/memex_web/live/pipeline_live/form_component.html.heex:37
#, elixir-autogen, elixir-format
msgid "select privacy"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:79
#, elixir-autogen, elixir-format
msgid "set unlimited"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:3
#, elixir-autogen, elixir-format
msgid "settings"
msgstr ""
#: lib/memex_web/live/context_live/form_component.html.heex:30
#: lib/memex_web/live/note_live/form_component.html.heex:30
#: lib/memex_web/live/pipeline_live/form_component.html.heex:30
#, elixir-autogen, elixir-format
msgid "tag1,tag2"
msgstr ""
#: lib/memex_web/components/contexts_table_component.ex:48
#: lib/memex_web/components/notes_table_component.ex:48
#: lib/memex_web/components/pipelines_table_component.ex:49
#, elixir-autogen, elixir-format
msgid "tags"
msgstr ""
#: lib/memex_web/components/invite_card.ex:20
#, elixir-autogen, elixir-format
msgid "unlimited"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:120
#, elixir-autogen, elixir-format
msgid "users"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:121
#, elixir-autogen, elixir-format
msgid "version:"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:148
#, elixir-autogen, elixir-format
msgid "view the source code"
msgstr ""
#: lib/memex_web/components/contexts_table_component.ex:49
#: lib/memex_web/components/notes_table_component.ex:49
#: lib/memex_web/components/pipelines_table_component.ex:50
#, elixir-autogen, elixir-format
msgid "visibility"
msgstr ""
#: lib/memex_web/live/note_live/index.ex:29
#, elixir-autogen, elixir-format
msgid "new note"
msgstr ""
#: lib/memex_web/live/context_live/index.html.heex:17
#: lib/memex_web/live/note_live/index.html.heex:17
#: lib/memex_web/live/pipeline_live/index.html.heex:17
#, elixir-autogen, elixir-format
msgid "search"
msgstr ""
#: lib/memex_web/live/context_live/index.ex:29
#, elixir-autogen, elixir-format
msgid "new context"
msgstr ""
#: lib/memex_web/live/context_live/index.html.heex:23
#, elixir-autogen, elixir-format
msgid "no contexts found"
msgstr ""
#: lib/memex_web/components/pipelines_table_component.ex:48
#: lib/memex_web/live/pipeline_live/form_component.html.heex:23
#, elixir-autogen, elixir-format
msgid "description"
msgstr ""
#: lib/memex_web/live/pipeline_live/index.ex:29
#, elixir-autogen, elixir-format
msgid "new pipeline"
msgstr ""
#: lib/memex_web/live/pipeline_live/index.html.heex:23
#, elixir-autogen, elixir-format
msgid "no pipelines found"
msgstr ""
#: lib/memex_web/live/context_live/form_component.ex:61
#: lib/memex_web/live/note_live/form_component.ex:60
#: lib/memex_web/live/pipeline_live/form_component.ex:65
#, elixir-autogen, elixir-format
msgid "%{slug} created"
msgstr ""
#: lib/memex_web/live/context_live/index.ex:57
#: lib/memex_web/live/context_live/show.ex:41
#: lib/memex_web/live/note_live/index.ex:57
#: lib/memex_web/live/note_live/show.ex:41
#: lib/memex_web/live/pipeline_live/index.ex:57
#: lib/memex_web/live/pipeline_live/show.ex:77
#, elixir-autogen, elixir-format
msgid "%{slug} deleted"
msgstr ""
#: lib/memex_web/live/context_live/form_component.ex:44
#: lib/memex_web/live/note_live/form_component.ex:43
#: lib/memex_web/live/pipeline_live/form_component.ex:48
#, elixir-autogen, elixir-format
msgid "%{slug} saved"
msgstr ""
#: lib/memex_web/live/context_live/index.ex:23
#: lib/memex_web/live/context_live/show.ex:48
#: lib/memex_web/live/note_live/index.ex:23
#: lib/memex_web/live/note_live/show.ex:48
#: lib/memex_web/live/pipeline_live/index.ex:23
#: lib/memex_web/live/pipeline_live/show.ex:125
#, elixir-autogen, elixir-format
msgid "edit %{slug}"
msgstr ""
#: lib/memex_web/components/contexts_table_component.ex:47
#: lib/memex_web/components/notes_table_component.ex:47
#: lib/memex_web/components/pipelines_table_component.ex:47
#: lib/memex_web/live/context_live/form_component.html.heex:14
#: lib/memex_web/live/note_live/form_component.html.heex:14
#: lib/memex_web/live/pipeline_live/form_component.html.heex:14
#, elixir-autogen, elixir-format
msgid "slug"
msgstr ""
#: lib/memex_web/live/context_live/show.ex:19
#: lib/memex_web/live/note_live/show.ex:19
#: lib/memex_web/live/pipeline_live/show.ex:20
#, elixir-autogen, elixir-format
msgid "%{slug} could not be found"
msgstr ""
#: lib/memex_web/live/home_live.ex:15
#, elixir-autogen, elixir-format
msgid "home"
msgstr ""
#: lib/memex_web/live/context_live/form_component.html.heex:23
#, elixir-autogen, elixir-format
msgid "use [[note-slug]] to link to a note"
msgstr ""
#: lib/memex_web/live/faq_live.ex:10
#: lib/memex_web/live/faq_live.html.heex:3
#, elixir-autogen, elixir-format
msgid "faq"
msgstr ""
#: lib/memex_web/components/topbar.ex:23
#: lib/memex_web/live/home_live.html.heex:3
#: lib/memex_web/templates/layout/root.html.heex:8
#: lib/memex_web/templates/layout/root.html.heex:9
#: lib/memex_web/views/layout_view.ex:11
#, elixir-autogen, elixir-format
msgid "memEx"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:41
#, elixir-autogen, elixir-format
msgid "read more on how to use %{name}"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:11
#, elixir-autogen, elixir-format
msgid "what is this?"
msgstr ""
#: lib/memex_web/live/pipeline_live/show.html.heex:68
#, elixir-autogen, elixir-format
msgid "%{position}. %{title}"
msgstr ""
#: lib/memex_web/live/step_live/form_component.ex:67
#, elixir-autogen, elixir-format
msgid "%{title} created"
msgstr ""
#: lib/memex_web/live/pipeline_live/show.ex:96
#, elixir-autogen, elixir-format
msgid "%{title} deleted"
msgstr ""
#: lib/memex_web/live/step_live/form_component.ex:43
#, elixir-autogen, elixir-format
msgid "%{title} saved"
msgstr ""
#: lib/memex_web/live/pipeline_live/show.ex:127
#, elixir-autogen, elixir-format
msgid "add step to %{slug}"
msgstr ""
#: lib/memex_web/live/pipeline_live/show.html.heex:62
#, elixir-autogen, elixir-format
msgid "no steps"
msgstr ""
#: lib/memex_web/live/pipeline_live/show.html.heex:57
#, elixir-autogen, elixir-format
msgid "steps:"
msgstr ""
#: lib/memex_web/live/step_live/form_component.html.heex:14
#, elixir-autogen, elixir-format
msgid "title"
msgstr ""
#: lib/memex_web/live/step_live/form_component.html.heex:23
#, elixir-autogen, elixir-format
msgid "use [[context-slug]] to link to a context"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:72
#, elixir-autogen, elixir-format
msgid "finally, i wanted to externalize the processes for common situations that use these thought processes at discrete steps. these are pipelines!"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:102
#, elixir-autogen, elixir-format
msgid "for instance, a good context could be what makes some physical designs spark joy for you, and in that context you could backlink to the spoon note as an example of how it fits nicely into your hand."
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:118
#, elixir-autogen, elixir-format
msgid "for instance, a pipeline for buying an object could have a step where you consider how much it sparks joy, and it could backlink to the physical designs context, maybe with some notes about how it applies in this case."
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:62
#, elixir-autogen, elixir-format
msgid "i really admired the idea of a zettelkasten, especially with org-mode backlinks, however I felt like my notes would immediately become too messy by just putting everything into a single hierarchy."
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:67
#, elixir-autogen, elixir-format
msgid "i wanted to separate between a personal dictionary of concepts and then my thought processes that are built off of my experiences and life lessons. these are notes, and contexts, respectively."
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:99
#, elixir-autogen, elixir-format
msgid "in my opinion, contexts should be like single-topic blog posts."
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:83
#, elixir-autogen, elixir-format
msgid "in my opinion, notes should be written by any of the discrete objects or concepts that are meaningful to you in your life."
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:113
#, elixir-autogen, elixir-format
msgid "in my opinion, pipelines should be pretty lightweight, and just backlink to contexts to provide most of the heavy lifting."
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:31
#, elixir-autogen, elixir-format
msgid "memex"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:51
#, elixir-autogen, elixir-format
msgid "org-mode"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:20
#, elixir-autogen, elixir-format
msgid "some things that this memex is very loosely inspired by:"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:88
#, elixir-autogen, elixir-format
msgid "spoons? probably not. a particular brand of spoons that you really like? why not :)"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:14
#, elixir-autogen, elixir-format
msgid "this is a memex, used to document not just your notes, but also your perspectives and processes."
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:96
#, elixir-autogen, elixir-format
msgid "what should my contexts be like?"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:80
#, elixir-autogen, elixir-format
msgid "what should my notes be like?"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:110
#, elixir-autogen, elixir-format
msgid "what should my pipelines be like?"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:59
#, elixir-autogen, elixir-format
msgid "why split up into notes, contexts and pipelines?"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:41
#, elixir-autogen, elixir-format
msgid "zettelkasten"
msgstr ""
#: lib/memex_web/views/layout_view.ex:10
#, elixir-autogen, elixir-format
msgid "memEx | %{title}"
msgstr ""
#: lib/memex_web/controllers/user_reset_password_controller.ex:9
#, elixir-autogen, elixir-format, fuzzy
msgid "forgot your password?"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:126
#, elixir-autogen, elixir-format
msgid "how many people should i invite?"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:134
#, elixir-autogen, elixir-format
msgid "note, context and pipeline slugs must be unique, and you are free to backlink to notes not written by you."
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:139
#, elixir-autogen, elixir-format
msgid "so, i'd recommend inviting anyone you'd like to work on your collective memEx. however, when in doubt, hopefully setting up a new instance is easy enough. if it isn't, then feel free to let me know :)"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:129
#, elixir-autogen, elixir-format
msgid "while memEx fully supports multiple users, each memEx instance should be treated as a single cohesive and collaborative document."
msgstr ""
#: lib/memex_web/components/user_card.ex:26
#, elixir-autogen, elixir-format
msgid "user confirmed on"
msgstr ""
#: lib/memex_web/components/user_card.ex:33
#, elixir-autogen, elixir-format, fuzzy
msgid "user registered on"
msgstr ""

View File

@ -1,93 +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: de\n"
"Plural-Forms: nplurals=2\n"
#: lib/memex/accounts/email.ex:30
#, elixir-autogen, elixir-format
msgid "Confirm your Memex account"
msgstr ""
#: lib/memex_web/templates/email/confirm_email.html.heex:3
#: lib/memex_web/templates/email/confirm_email.txt.eex:2
#: lib/memex_web/templates/email/reset_password.html.heex:3
#: lib/memex_web/templates/email/reset_password.txt.eex:2
#: lib/memex_web/templates/email/update_email.html.heex:3
#: lib/memex_web/templates/email/update_email.txt.eex:2
#, elixir-autogen, elixir-format
msgid "Hi %{email},"
msgstr ""
#: lib/memex_web/templates/email/confirm_email.txt.eex:10
#, elixir-autogen, elixir-format
msgid "If you didn't create an account at %{url}, please ignore this."
msgstr ""
#: lib/memex_web/templates/email/reset_password.txt.eex:8
#: lib/memex_web/templates/email/update_email.txt.eex:8
#, elixir-autogen, elixir-format
msgid "If you didn't request this change from %{url}, please ignore this."
msgstr ""
#: lib/memex/accounts/email.ex:37
#, elixir-autogen, elixir-format
msgid "Reset your Memex password"
msgstr ""
#: lib/memex/accounts/email.ex:44
#, elixir-autogen, elixir-format
msgid "Update your Memex email"
msgstr ""
#: lib/memex_web/templates/email/update_email.html.heex:8
#: lib/memex_web/templates/email/update_email.txt.eex:4
#, elixir-autogen, elixir-format
msgid "You can change your email by visiting the URL below:"
msgstr ""
#: lib/memex_web/templates/email/confirm_email.html.heex:14
#: lib/memex_web/templates/email/confirm_email.txt.eex:6
#, elixir-autogen, elixir-format
msgid "You can confirm your account by visiting the URL below:"
msgstr ""
#: lib/memex_web/templates/email/reset_password.html.heex:8
#: lib/memex_web/templates/email/reset_password.txt.eex:4
#, elixir-autogen, elixir-format
msgid "You can reset your password by visiting the URL below:"
msgstr ""
#: lib/memex_web/templates/layout/email.html.heex:13
#, elixir-autogen, elixir-format
msgid "This email was sent from memEx"
msgstr ""
#: lib/memex_web/templates/layout/email.txt.eex:9
#, elixir-autogen, elixir-format
msgid "This email was sent from memEx at %{url}"
msgstr ""
#: lib/memex_web/templates/email/confirm_email.html.heex:22
#, elixir-autogen, elixir-format, fuzzy
msgid "If you didn't create an account at memEx, please ignore this."
msgstr ""
#: lib/memex_web/templates/email/reset_password.html.heex:16
#: lib/memex_web/templates/email/update_email.html.heex:16
#, elixir-autogen, elixir-format, fuzzy
msgid "If you didn't request this change from memEx, please ignore this."
msgstr ""
#: lib/memex_web/templates/email/confirm_email.html.heex:9
#: lib/memex_web/templates/email/confirm_email.txt.eex:4
#, elixir-autogen, elixir-format, fuzzy
msgid "Welcome to memEx"
msgstr ""

View File

@ -1,140 +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: de\n"
"Plural-Forms: nplurals=2\n"
#: lib/memex_web/controllers/user_settings_controller.ex:84
#, elixir-autogen, elixir-format
msgid "Email change link is invalid or it has expired."
msgstr ""
#: lib/memex_web/templates/error/error.html.heex:8
#, elixir-autogen, elixir-format
msgid "Error"
msgstr ""
#: lib/memex_web/controllers/user_session_controller.ex:17
#, elixir-autogen, elixir-format
msgid "Invalid email or password"
msgstr ""
#: lib/memex_web/templates/user_registration/new.html.heex:15
#: lib/memex_web/templates/user_reset_password/edit.html.heex:15
#: lib/memex_web/templates/user_settings/edit.html.heex:64
#, elixir-autogen, elixir-format
msgid "Oops, something went wrong! Please check the errors below."
msgstr ""
#: lib/memex_web/controllers/user_reset_password_controller.ex:63
#, elixir-autogen, elixir-format
msgid "Reset password link is invalid or it has expired."
msgstr ""
#: lib/memex_web/controllers/user_registration_controller.ex:24
#: lib/memex_web/controllers/user_registration_controller.ex:55
#, elixir-autogen, elixir-format
msgid "Sorry, public registration is disabled"
msgstr ""
#: lib/memex_web/controllers/user_registration_controller.ex:14
#: lib/memex_web/controllers/user_registration_controller.ex:45
#, elixir-autogen, elixir-format
msgid "Sorry, this invite was not found or expired"
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:99
#, elixir-autogen, elixir-format
msgid "Unable to delete user"
msgstr ""
#: lib/memex_web/controllers/user_confirmation_controller.ex:54
#, elixir-autogen, elixir-format
msgid "User confirmation link is invalid or it has expired."
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:18
#, elixir-autogen, elixir-format
msgid "You are not authorized to view this page"
msgstr ""
#: lib/memex_web/controllers/user_auth.ex:177
#, elixir-autogen, elixir-format
msgid "You are not authorized to view this page."
msgstr ""
#: lib/memex_web/controllers/user_auth.ex:39
#: lib/memex_web/controllers/user_auth.ex:161
#, elixir-autogen, elixir-format
msgid "You must confirm your account and log in to access this page."
msgstr ""
#: lib/memex/accounts/user.ex:129
#, elixir-autogen, elixir-format
msgid "did not change"
msgstr ""
#: lib/memex/accounts/user.ex:150
#, elixir-autogen, elixir-format
msgid "does not match password"
msgstr ""
#: lib/memex/accounts/user.ex:187
#, elixir-autogen, elixir-format
msgid "is not valid"
msgstr ""
#: lib/memex/accounts/user.ex:85
#, elixir-autogen, elixir-format
msgid "must have the @ sign and no spaces"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:21
#: lib/memex_web/templates/user_settings/edit.html.heex:119
#, elixir-autogen, elixir-format
msgid "oops, something went wrong! Please check the errors below"
msgstr ""
#: lib/memex/contexts/context.ex:49
#: lib/memex/contexts/context.ex:62
#: lib/memex/notes/note.ex:48
#: lib/memex/notes/note.ex:61
#: lib/memex/pipelines/pipeline.ex:50
#: lib/memex/pipelines/pipeline.ex:63
#, elixir-autogen, elixir-format
msgid "invalid format: only numbers, letters and hyphen are accepted"
msgstr ""
#: lib/memex_web/templates/error/error.html.heex:28
#, elixir-autogen, elixir-format
msgid "go back home"
msgstr ""
#: lib/memex_web/views/error_view.ex:11
#, elixir-autogen, elixir-format
msgid "internal server error"
msgstr ""
#: lib/memex_web/views/error_view.ex:9
#, elixir-autogen, elixir-format
msgid "not found"
msgstr ""
#: lib/memex_web/views/error_view.ex:10
#, elixir-autogen, elixir-format
msgid "unauthorized"
msgstr ""
#: lib/memex/contexts/context.ex:75
#: lib/memex/notes/note.ex:74
#: lib/memex/pipelines/pipeline.ex:76
#, elixir-autogen, elixir-format, fuzzy
msgid "invalid format: only numbers, letters and hyphen are accepted. tags must be comma-delimited"
msgstr ""

View File

@ -1,159 +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: de\n"
"Plural-Forms: nplurals=2\n"
#: lib/memex_web/controllers/user_confirmation_controller.ex:38
#, elixir-autogen, elixir-format
msgid "%{email} confirmed successfully."
msgstr ""
#: lib/memex_web/live/invite_live/form_component.ex:62
#, elixir-autogen, elixir-format
msgid "%{invite_name} created successfully"
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:53
#, elixir-autogen, elixir-format
msgid "%{invite_name} deleted succesfully"
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:114
#, elixir-autogen, elixir-format
msgid "%{invite_name} disabled succesfully"
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:90
#, elixir-autogen, elixir-format
msgid "%{invite_name} enabled succesfully"
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:68
#, elixir-autogen, elixir-format
msgid "%{invite_name} updated succesfully"
msgstr ""
#: lib/memex_web/live/invite_live/form_component.ex:42
#, elixir-autogen, elixir-format
msgid "%{invite_name} updated successfully"
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:139
#, elixir-autogen, elixir-format
msgid "%{user_email} deleted succesfully"
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:29
#, elixir-autogen, elixir-format
msgid "A link to confirm your email change has been sent to the new address."
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:127
#, elixir-autogen, elixir-format
msgid "Copied to clipboard"
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:77
#, elixir-autogen, elixir-format
msgid "Email changed successfully."
msgstr ""
#: lib/memex_web/controllers/user_confirmation_controller.ex:23
#, elixir-autogen, elixir-format
msgid "If your email is in our system and it has not been confirmed yet, you will receive an email with instructions shortly."
msgstr ""
#: lib/memex_web/controllers/user_reset_password_controller.ex:24
#, elixir-autogen, elixir-format
msgid "If your email is in our system, you will receive instructions to reset your password shortly."
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:65
#, elixir-autogen, elixir-format
msgid "Language updated successfully."
msgstr ""
#: lib/memex_web/controllers/user_reset_password_controller.ex:46
#, elixir-autogen, elixir-format
msgid "Password reset successfully."
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:49
#, elixir-autogen, elixir-format
msgid "Password updated successfully."
msgstr ""
#: lib/memex_web/controllers/user_registration_controller.ex:73
#, elixir-autogen, elixir-format
msgid "Please check your email to verify your account"
msgstr ""
#: lib/memex_web/live/invite_live/form_component.html.heex:30
#, elixir-autogen, elixir-format
msgid "Saving..."
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:95
#, elixir-autogen, elixir-format
msgid "Your account has been deleted"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:133
#, elixir-autogen, elixir-format
msgid "are you sure you want to change your language?"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:102
#: lib/memex_web/live/invite_live/index.html.heex:132
#, elixir-autogen, elixir-format
msgid "are you sure you want to delete %{email}? This action is permanent!"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:48
#, elixir-autogen, elixir-format
msgid "are you sure you want to delete the invite for %{invite_name}?"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:143
#, elixir-autogen, elixir-format
msgid "are you sure you want to delete your account?"
msgstr ""
#: lib/memex_web/components/topbar.ex:92
#, elixir-autogen, elixir-format
msgid "are you sure you want to log out?"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:74
#, elixir-autogen, elixir-format
msgid "are you sure you want to make %{invite_name} unlimited?"
msgstr ""
#: lib/memex_web/live/context_live/index.html.heex:46
#: lib/memex_web/live/context_live/show.html.heex:37
#: lib/memex_web/live/note_live/index.html.heex:46
#: lib/memex_web/live/note_live/show.html.heex:34
#: lib/memex_web/live/pipeline_live/index.html.heex:46
#: lib/memex_web/live/pipeline_live/show.html.heex:46
#: lib/memex_web/live/pipeline_live/show.html.heex:116
#, elixir-autogen, elixir-format
msgid "are you sure?"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:95
#, elixir-autogen, elixir-format
msgid "register to setup %{name}"
msgstr ""
#: lib/memex_web/controllers/user_session_controller.ex:23
#, elixir-autogen, elixir-format, fuzzy
msgid "logged out successfully."
msgstr ""

View File

@ -10,647 +10,297 @@
msgid ""
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:90
#, elixir-autogen, elixir-format
#: lib/memex_web/live/home_live.html.heex:73
msgid "Accessible from any internet-capable device"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/index.html.heex:89
msgid "Admins"
msgstr ""
#: lib/memex_web/controllers/user_confirmation_controller.ex:8
#, elixir-autogen, elixir-format
#: lib/memex_web/live/home_live.html.heex:53
msgid "Built with sharing and collaboration in mind"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/user_settings/edit.html.heex:78
msgid "Confirm new password"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_confirmation_controller.ex:8
msgid "Confirm your account"
msgstr ""
#: lib/memex_web/templates/user_session/new.html.heex:27
#, elixir-autogen, elixir-format
#: lib/memex_web/components/topbar.ex:63
msgid "Contexts"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/home_live.html.heex:22
msgid "Contexts:"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/home_live.html.heex:70
msgid "Convenient:"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/user_settings/edit.html.heex:32
#: lib/memex_web/templates/user_settings/edit.html.heex:87
msgid "Current password"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/index.html.heex:58
msgid "Disable"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/home_live.html.heex:15
msgid "Document notes about individual items or concepts"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/home_live.html.heex:35
msgid "Document your processes, attaching contexts to each step"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/index.ex:33
msgid "Edit Invite"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/index.html.heex:62
msgid "Enable"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/user_registration/new.html.heex:36
#: lib/memex_web/templates/user_settings/edit.html.heex:126
msgid "English"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/home_live.html.heex:44
msgid "Features"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_reset_password_controller.ex:9
msgid "Forgot your password?"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/home_live.ex:12
msgid "Home"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/components/invite_card.ex:25
msgid "Invite Disabled"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/components/topbar.ex:78
#: lib/memex_web/live/invite_live/index.ex:41
#: lib/memex_web/live/invite_live/index.html.heex:3
msgid "Invites"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/user_session/new.html.heex:27
msgid "Keep me logged in for 60 days"
msgstr ""
#: lib/memex_web/templates/user_registration/new.html.heex:32
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/user_registration/new.html.heex:32
msgid "Language"
msgstr ""
#: lib/memex_web/templates/layout/live.html.heex:37
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/layout/live.html.heex:37
msgid "Loading..."
msgstr ""
#: lib/memex_web/live/invite_live/form_component.html.heex:20
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_session_controller.ex:8
msgid "Log in"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/home_live.html.heex:50
msgid "Multi-user:"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/form_component.html.heex:20
msgid "Name"
msgstr ""
#: lib/memex_web/templates/layout/live.html.heex:50
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/index.ex:37
msgid "New Invite"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/user_settings/edit.html.heex:71
msgid "New password"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/index.html.heex:8
msgid "No invites 😔"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/components/topbar.ex:70
msgid "Notes"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/home_live.html.heex:12
msgid "Notes:"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/components/topbar.ex:56
msgid "Pipelines"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/home_live.html.heex:32
msgid "Pipelines:"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/home_live.html.heex:63
msgid "Privacy controls on a per-note, context or pipeline basis"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/home_live.html.heex:60
msgid "Privacy:"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/home_live.html.heex:25
msgid "Provide context around a single topic and hotlink to your notes"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/layout/live.html.heex:50
msgid "Reconnecting..."
msgstr ""
#: lib/memex_web/controllers/user_reset_password_controller.ex:36
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_registration_controller.ex:35
msgid "Register"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_reset_password_controller.ex:36
msgid "Reset your password"
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:10
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/index.html.heex:78
msgid "Set Unlimited"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_settings_controller.ex:10
#: lib/memex_web/templates/user_settings/edit.html.heex:3
msgid "Settings"
msgstr ""
#: lib/memex_web/components/invite_card.ex:19
#, elixir-autogen, elixir-format
#: lib/memex_web/components/user_card.ex:30
msgid "User registered on"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/index.html.heex:118
msgid "Users"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/components/invite_card.ex:20
msgid "Uses Left:"
msgstr ""
#: lib/memex_web/live/invite_live/form_component.html.heex:24
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/form_component.html.heex:24
msgid "Uses left"
msgstr ""
#: lib/memex_web/live/context_live/show.html.heex:17
#: lib/memex_web/live/note_live/show.html.heex:17
#: lib/memex_web/live/pipeline_live/show.html.heex:26
#, elixir-autogen, elixir-format
msgid "Visibility: %{visibility}"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:76
#, elixir-autogen, elixir-format
msgid "accessible from any internet-capable device"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:90
#, elixir-autogen, elixir-format
msgid "admins:"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:58
#, elixir-autogen, elixir-format
msgid "built with sharing and collaboration in mind"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:78
#, elixir-autogen, elixir-format
msgid "confirm new password"
msgstr ""
#: lib/memex_web/live/note_live/form_component.html.heex:23
#, elixir-autogen, elixir-format
msgid "content"
msgstr ""
#: lib/memex_web/components/topbar.ex:52
#: lib/memex_web/live/context_live/index.ex:35
#: lib/memex_web/live/context_live/index.ex:43
#: lib/memex_web/live/context_live/index.html.heex:3
#, elixir-autogen, elixir-format
msgid "contexts"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:20
#, elixir-autogen, elixir-format
msgid "contexts:"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:73
#, elixir-autogen, elixir-format
msgid "convenient:"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:32
#: lib/memex_web/templates/user_settings/edit.html.heex:87
#, elixir-autogen, elixir-format
msgid "current password"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:59
#, elixir-autogen, elixir-format
msgid "disable"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:14
#, elixir-autogen, elixir-format
msgid "document notes about individual items or concepts"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:32
#, elixir-autogen, elixir-format
msgid "document your processes, attaching contexts to each step"
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:33
#, elixir-autogen, elixir-format
msgid "edit invite"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:28
#, elixir-autogen, elixir-format
msgid "email"
msgstr ""
#: lib/memex_web/components/user_card.ex:23
#, elixir-autogen, elixir-format
msgid "email unconfirmed"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:63
#, elixir-autogen, elixir-format
msgid "enable"
msgstr ""
#: lib/memex_web/templates/user_registration/new.html.heex:36
#: lib/memex_web/templates/user_settings/edit.html.heex:126
#, elixir-autogen, elixir-format
msgid "english"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:50
#, elixir-autogen, elixir-format
msgid "features"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:138
#, elixir-autogen, elixir-format
msgid "get involved!"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:159
#, elixir-autogen, elixir-format
msgid "help translate"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:85
#, elixir-autogen, elixir-format
msgid "instance information"
msgstr ""
#: lib/memex_web/components/invite_card.ex:24
#, elixir-autogen, elixir-format
msgid "invite disabled"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:115
#, elixir-autogen, elixir-format
msgid "invite only"
msgstr ""
#: lib/memex_web/components/topbar.ex:74
#: lib/memex_web/live/invite_live/index.ex:41
#: lib/memex_web/live/invite_live/index.html.heex:3
#, elixir-autogen, elixir-format
msgid "invites"
msgstr ""
#: lib/memex_web/controllers/user_session_controller.ex:8
#, elixir-autogen, elixir-format
msgid "log in"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:55
#, elixir-autogen, elixir-format
msgid "multi-user:"
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:37
#, elixir-autogen, elixir-format
msgid "new invite"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:71
#, elixir-autogen, elixir-format
msgid "new password"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:8
#, elixir-autogen, elixir-format
msgid "no invites 😔"
msgstr ""
#: lib/memex_web/live/note_live/index.html.heex:23
#, elixir-autogen, elixir-format
msgid "no notes found"
msgstr ""
#: lib/memex_web/components/topbar.ex:43
#: lib/memex_web/live/note_live/index.ex:35
#: lib/memex_web/live/note_live/index.ex:43
#: lib/memex_web/live/note_live/index.html.heex:3
#, elixir-autogen, elixir-format
msgid "notes"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:11
#, elixir-autogen, elixir-format
msgid "notes:"
msgstr ""
#: lib/memex_web/components/topbar.ex:61
#: lib/memex_web/live/pipeline_live/index.ex:35
#: lib/memex_web/live/pipeline_live/index.ex:43
#: lib/memex_web/live/pipeline_live/index.html.heex:3
#, elixir-autogen, elixir-format
msgid "pipelines"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:29
#, elixir-autogen, elixir-format
msgid "pipelines:"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:67
#, elixir-autogen, elixir-format
msgid "privacy controls on a per-note, context or pipeline basis"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:64
#, elixir-autogen, elixir-format
msgid "privacy:"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:23
#, elixir-autogen, elixir-format
msgid "provide context around a single topic and hotlink to your notes"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:114
#, elixir-autogen, elixir-format
msgid "public signups"
msgstr ""
#: lib/memex_web/controllers/user_registration_controller.ex:34
#, elixir-autogen, elixir-format
msgid "register"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:110
#, elixir-autogen, elixir-format
msgid "registration:"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:170
#, elixir-autogen, elixir-format
msgid "report bugs or request features"
msgstr ""
#: lib/memex_web/live/context_live/form_component.html.heex:41
#: lib/memex_web/live/note_live/form_component.html.heex:41
#: lib/memex_web/live/pipeline_live/form_component.html.heex:41
#: lib/memex_web/live/step_live/form_component.html.heex:29
#, elixir-autogen, elixir-format
msgid "saving..."
msgstr ""
#: lib/memex_web/live/context_live/form_component.html.heex:37
#: lib/memex_web/live/note_live/form_component.html.heex:37
#: lib/memex_web/live/pipeline_live/form_component.html.heex:37
#, elixir-autogen, elixir-format
msgid "select privacy"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:79
#, elixir-autogen, elixir-format
msgid "set unlimited"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:3
#, elixir-autogen, elixir-format
msgid "settings"
msgstr ""
#: lib/memex_web/live/context_live/form_component.html.heex:30
#: lib/memex_web/live/note_live/form_component.html.heex:30
#: lib/memex_web/live/pipeline_live/form_component.html.heex:30
#, elixir-autogen, elixir-format
msgid "tag1,tag2"
msgstr ""
#: lib/memex_web/components/contexts_table_component.ex:48
#: lib/memex_web/components/notes_table_component.ex:48
#: lib/memex_web/components/pipelines_table_component.ex:49
#, elixir-autogen, elixir-format
msgid "tags"
msgstr ""
#: lib/memex_web/components/invite_card.ex:20
#, elixir-autogen, elixir-format
msgid "unlimited"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:120
#, elixir-autogen, elixir-format
msgid "users"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:121
#, elixir-autogen, elixir-format
msgid "version:"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:148
#, elixir-autogen, elixir-format
msgid "view the source code"
msgstr ""
#: lib/memex_web/components/contexts_table_component.ex:49
#: lib/memex_web/components/notes_table_component.ex:49
#: lib/memex_web/components/pipelines_table_component.ex:50
#, elixir-autogen, elixir-format
msgid "visibility"
msgstr ""
#: lib/memex_web/live/note_live/index.ex:29
#, elixir-autogen, elixir-format
msgid "new note"
msgstr ""
#: lib/memex_web/live/context_live/index.html.heex:17
#: lib/memex_web/live/note_live/index.html.heex:17
#: lib/memex_web/live/pipeline_live/index.html.heex:17
#, elixir-autogen, elixir-format
msgid "search"
msgstr ""
#: lib/memex_web/live/context_live/index.ex:29
#, elixir-autogen, elixir-format
msgid "new context"
msgstr ""
#: lib/memex_web/live/context_live/index.html.heex:23
#, elixir-autogen, elixir-format
msgid "no contexts found"
msgstr ""
#: lib/memex_web/components/pipelines_table_component.ex:48
#: lib/memex_web/live/pipeline_live/form_component.html.heex:23
#, elixir-autogen, elixir-format
msgid "description"
msgstr ""
#: lib/memex_web/live/pipeline_live/index.ex:29
#, elixir-autogen, elixir-format
msgid "new pipeline"
msgstr ""
#: lib/memex_web/live/pipeline_live/index.html.heex:23
#, elixir-autogen, elixir-format
msgid "no pipelines found"
msgstr ""
#: lib/memex_web/live/context_live/form_component.ex:61
#: lib/memex_web/live/note_live/form_component.ex:60
#: lib/memex_web/live/pipeline_live/form_component.ex:65
#, elixir-autogen, elixir-format
msgid "%{slug} created"
msgstr ""
#: lib/memex_web/live/context_live/index.ex:57
#: lib/memex_web/live/context_live/show.ex:41
#: lib/memex_web/live/note_live/index.ex:57
#: lib/memex_web/live/note_live/show.ex:41
#: lib/memex_web/live/pipeline_live/index.ex:57
#: lib/memex_web/live/pipeline_live/show.ex:77
#, elixir-autogen, elixir-format
msgid "%{slug} deleted"
msgstr ""
#: lib/memex_web/live/context_live/form_component.ex:44
#: lib/memex_web/live/note_live/form_component.ex:43
#: lib/memex_web/live/pipeline_live/form_component.ex:48
#, elixir-autogen, elixir-format
msgid "%{slug} saved"
msgstr ""
#: lib/memex_web/live/context_live/index.ex:23
#: lib/memex_web/live/context_live/show.ex:48
#: lib/memex_web/live/note_live/index.ex:23
#: lib/memex_web/live/note_live/show.ex:48
#: lib/memex_web/live/pipeline_live/index.ex:23
#: lib/memex_web/live/pipeline_live/show.ex:125
#, elixir-autogen, elixir-format
msgid "edit %{slug}"
msgstr ""
#: lib/memex_web/components/contexts_table_component.ex:47
#: lib/memex_web/components/notes_table_component.ex:47
#: lib/memex_web/components/pipelines_table_component.ex:47
#: lib/memex_web/live/context_live/form_component.html.heex:14
#: lib/memex_web/live/note_live/form_component.html.heex:14
#: lib/memex_web/live/pipeline_live/form_component.html.heex:14
#, elixir-autogen, elixir-format
msgid "slug"
msgstr ""
#: lib/memex_web/live/context_live/show.ex:19
#: lib/memex_web/live/note_live/show.ex:19
#: lib/memex_web/live/pipeline_live/show.ex:20
#, elixir-autogen, elixir-format
msgid "%{slug} could not be found"
msgstr ""
#: lib/memex_web/live/home_live.ex:15
#, elixir-autogen, elixir-format
msgid "home"
msgstr ""
#: lib/memex_web/live/context_live/form_component.html.heex:23
#, elixir-autogen, elixir-format
msgid "use [[note-slug]] to link to a note"
msgstr ""
#: lib/memex_web/live/faq_live.ex:10
#: lib/memex_web/live/faq_live.html.heex:3
#, elixir-autogen, elixir-format
msgid "faq"
msgstr ""
#: lib/memex_web/components/topbar.ex:23
#: lib/memex_web/live/home_live.html.heex:3
#: lib/memex_web/templates/layout/root.html.heex:8
#: lib/memex_web/templates/layout/root.html.heex:9
#: lib/memex_web/views/layout_view.ex:11
#, elixir-autogen, elixir-format
msgid "memEx"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:41
#, elixir-autogen, elixir-format
msgid "read more on how to use %{name}"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:11
#, elixir-autogen, elixir-format
msgid "what is this?"
msgstr ""
#: lib/memex_web/live/pipeline_live/show.html.heex:68
#, elixir-autogen, elixir-format
msgid "%{position}. %{title}"
msgstr ""
#: lib/memex_web/live/step_live/form_component.ex:67
#, elixir-autogen, elixir-format
msgid "%{title} created"
msgstr ""
#: lib/memex_web/live/pipeline_live/show.ex:96
#, elixir-autogen, elixir-format
msgid "%{title} deleted"
msgstr ""
#: lib/memex_web/live/step_live/form_component.ex:43
#, elixir-autogen, elixir-format
msgid "%{title} saved"
msgstr ""
#: lib/memex_web/live/pipeline_live/show.ex:127
#, elixir-autogen, elixir-format
msgid "add step to %{slug}"
msgstr ""
#: lib/memex_web/live/pipeline_live/show.html.heex:62
#, elixir-autogen, elixir-format
msgid "no steps"
msgstr ""
#: lib/memex_web/live/pipeline_live/show.html.heex:57
#, elixir-autogen, elixir-format
msgid "steps:"
msgstr ""
#: lib/memex_web/live/step_live/form_component.html.heex:14
#, elixir-autogen, elixir-format
msgid "title"
msgstr ""
#: lib/memex_web/live/step_live/form_component.html.heex:23
#, elixir-autogen, elixir-format
msgid "use [[context-slug]] to link to a context"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:72
#, elixir-autogen, elixir-format
msgid "finally, i wanted to externalize the processes for common situations that use these thought processes at discrete steps. these are pipelines!"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:102
#, elixir-autogen, elixir-format
msgid "for instance, a good context could be what makes some physical designs spark joy for you, and in that context you could backlink to the spoon note as an example of how it fits nicely into your hand."
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:118
#, elixir-autogen, elixir-format
msgid "for instance, a pipeline for buying an object could have a step where you consider how much it sparks joy, and it could backlink to the physical designs context, maybe with some notes about how it applies in this case."
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:62
#, elixir-autogen, elixir-format
msgid "i really admired the idea of a zettelkasten, especially with org-mode backlinks, however I felt like my notes would immediately become too messy by just putting everything into a single hierarchy."
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:67
#, elixir-autogen, elixir-format
msgid "i wanted to separate between a personal dictionary of concepts and then my thought processes that are built off of my experiences and life lessons. these are notes, and contexts, respectively."
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:99
#, elixir-autogen, elixir-format
msgid "in my opinion, contexts should be like single-topic blog posts."
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:83
#, elixir-autogen, elixir-format
msgid "in my opinion, notes should be written by any of the discrete objects or concepts that are meaningful to you in your life."
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:113
#, elixir-autogen, elixir-format
msgid "in my opinion, pipelines should be pretty lightweight, and just backlink to contexts to provide most of the heavy lifting."
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:31
#, elixir-autogen, elixir-format
msgid "memex"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:51
#, elixir-autogen, elixir-format
msgid "org-mode"
#: lib/memex_web/live/home_live.html.heex:87
msgid "Admins:"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:20
#, elixir-autogen, elixir-format
msgid "some things that this memex is very loosely inspired by:"
#: lib/memex_web/live/note_live/form_component.html.heex:20
msgid "Content"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:88
#, elixir-autogen, elixir-format
msgid "spoons? probably not. a particular brand of spoons that you really like? why not :)"
#: lib/memex_web/live/home_live.html.heex:134
msgid "Get involved!"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:14
#, elixir-autogen, elixir-format
msgid "this is a memex, used to document not just your notes, but also your perspectives and processes."
#: lib/memex_web/live/home_live.html.heex:151
msgid "Help translate"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:96
#, elixir-autogen, elixir-format
msgid "what should my contexts be like?"
#: lib/memex_web/live/home_live.html.heex:82
msgid "Instance Information"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:80
#, elixir-autogen, elixir-format
msgid "what should my notes be like?"
#: lib/memex_web/live/home_live.html.heex:113
msgid "Invite Only"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:110
#, elixir-autogen, elixir-format
msgid "what should my pipelines be like?"
#: lib/memex_web/live/home_live.html.heex:112
msgid "Public Signups"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:59
#, elixir-autogen, elixir-format
msgid "why split up into notes, contexts and pipelines?"
#: lib/memex_web/live/home_live.html.heex:160
msgid "Report bugs or request features"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:41
#, elixir-autogen, elixir-format
msgid "zettelkasten"
#: lib/memex_web/live/note_live/form_component.html.heex:35
msgid "Save"
msgstr ""
#: lib/memex_web/views/layout_view.ex:10
#, elixir-autogen, elixir-format
msgid "memEx | %{title}"
#: lib/memex_web/live/note_live/form_component.html.heex:36
msgid "Saving..."
msgstr ""
#: lib/memex_web/controllers/user_reset_password_controller.ex:9
#, elixir-autogen, elixir-format
msgid "forgot your password?"
#: lib/memex_web/live/note_live/form_component.html.heex:13
msgid "Title"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:126
#, elixir-autogen, elixir-format
msgid "how many people should i invite?"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:134
#, elixir-autogen, elixir-format
msgid "note, context and pipeline slugs must be unique, and you are free to backlink to notes not written by you."
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:139
#, elixir-autogen, elixir-format
msgid "so, i'd recommend inviting anyone you'd like to work on your collective memEx. however, when in doubt, hopefully setting up a new instance is easy enough. if it isn't, then feel free to let me know :)"
msgstr ""
#: lib/memex_web/live/faq_live.html.heex:129
#, elixir-autogen, elixir-format
msgid "while memEx fully supports multiple users, each memEx instance should be treated as a single cohesive and collaborative document."
msgstr ""
#: lib/memex_web/components/user_card.ex:26
#, elixir-autogen, elixir-format
msgid "user confirmed on"
msgstr ""
#: lib/memex_web/components/user_card.ex:33
#, elixir-autogen, elixir-format
msgid "user registered on"
#: lib/memex_web/live/home_live.html.heex:142
msgid "View the source code"
msgstr ""

View File

@ -10,83 +10,83 @@
msgid ""
msgstr ""
#: lib/memex/accounts/email.ex:30
#, elixir-autogen, elixir-format
#: lib/memex/accounts/email.ex:30
msgid "Confirm your Memex account"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/email/confirm_email.html.heex:3
#: lib/memex_web/templates/email/confirm_email.txt.eex:2
#: lib/memex_web/templates/email/reset_password.html.heex:3
#: lib/memex_web/templates/email/reset_password.txt.eex:2
#: lib/memex_web/templates/email/update_email.html.heex:3
#: lib/memex_web/templates/email/update_email.txt.eex:2
#, elixir-autogen, elixir-format
msgid "Hi %{email},"
msgstr ""
#: lib/memex_web/templates/email/confirm_email.txt.eex:10
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/email/confirm_email.txt.eex:10
msgid "If you didn't create an account at %{url}, please ignore this."
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/email/confirm_email.html.heex:22
msgid "If you didn't create an account at Memex, please ignore this."
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/email/reset_password.txt.eex:8
#: lib/memex_web/templates/email/update_email.txt.eex:8
#, elixir-autogen, elixir-format
msgid "If you didn't request this change from %{url}, please ignore this."
msgstr ""
#: lib/memex/accounts/email.ex:37
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/email/reset_password.html.heex:16
#: lib/memex_web/templates/email/update_email.html.heex:16
msgid "If you didn't request this change from Memex, please ignore this."
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex/accounts/email.ex:37
msgid "Reset your Memex password"
msgstr ""
#: lib/memex/accounts/email.ex:44
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/layout/email.txt.eex:9
msgid "This email was sent from Memex at %{url}, the self-hosted firearm tracker website."
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/layout/email.html.heex:13
msgid "This email was sent from Memex, the self-hosted firearm tracker website."
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex/accounts/email.ex:44
msgid "Update your Memex email"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/email/confirm_email.html.heex:9
#: lib/memex_web/templates/email/confirm_email.txt.eex:4
msgid "Welcome to Memex"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/email/update_email.html.heex:8
#: lib/memex_web/templates/email/update_email.txt.eex:4
#, elixir-autogen, elixir-format
msgid "You can change your email by visiting the URL below:"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/email/confirm_email.html.heex:14
#: lib/memex_web/templates/email/confirm_email.txt.eex:6
#, elixir-autogen, elixir-format
msgid "You can confirm your account by visiting the URL below:"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/email/reset_password.html.heex:8
#: lib/memex_web/templates/email/reset_password.txt.eex:4
#, elixir-autogen, elixir-format
msgid "You can reset your password by visiting the URL below:"
msgstr ""
#: lib/memex_web/templates/layout/email.html.heex:13
#, elixir-autogen, elixir-format
msgid "This email was sent from memEx"
msgstr ""
#: lib/memex_web/templates/layout/email.txt.eex:9
#, elixir-autogen, elixir-format
msgid "This email was sent from memEx at %{url}"
msgstr ""
#: lib/memex_web/templates/email/confirm_email.html.heex:22
#, elixir-autogen, elixir-format
msgid "If you didn't create an account at memEx, please ignore this."
msgstr ""
#: lib/memex_web/templates/email/reset_password.html.heex:16
#: lib/memex_web/templates/email/update_email.html.heex:16
#, elixir-autogen, elixir-format
msgid "If you didn't request this change from memEx, please ignore this."
msgstr ""
#: lib/memex_web/templates/email/confirm_email.html.heex:9
#: lib/memex_web/templates/email/confirm_email.txt.eex:4
#, elixir-autogen, elixir-format
msgid "Welcome to memEx"
msgstr ""

View File

@ -10,130 +10,109 @@
msgid ""
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:84
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_settings_controller.ex:84
msgid "Email change link is invalid or it has expired."
msgstr ""
#: lib/memex_web/templates/error/error.html.heex:8
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/error/error.html.heex:8
msgid "Error"
msgstr ""
#: lib/memex_web/controllers/user_session_controller.ex:17
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/error/error.html.heex:28
msgid "Go back home"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/views/error_view.ex:11
msgid "Internal Server Error"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_session_controller.ex:17
msgid "Invalid email or password"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/views/error_view.ex:9
msgid "Not found"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/user_registration/new.html.heex:15
#: lib/memex_web/templates/user_reset_password/edit.html.heex:15
#: lib/memex_web/templates/user_settings/edit.html.heex:21
#: lib/memex_web/templates/user_settings/edit.html.heex:64
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/user_settings/edit.html.heex:119
msgid "Oops, something went wrong! Please check the errors below."
msgstr ""
#: lib/memex_web/controllers/user_reset_password_controller.ex:63
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_reset_password_controller.ex:63
msgid "Reset password link is invalid or it has expired."
msgstr ""
#: lib/memex_web/controllers/user_registration_controller.ex:24
#: lib/memex_web/controllers/user_registration_controller.ex:55
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_registration_controller.ex:25
#: lib/memex_web/controllers/user_registration_controller.ex:56
msgid "Sorry, public registration is disabled"
msgstr ""
#: lib/memex_web/controllers/user_registration_controller.ex:14
#: lib/memex_web/controllers/user_registration_controller.ex:45
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_registration_controller.ex:15
#: lib/memex_web/controllers/user_registration_controller.ex:46
msgid "Sorry, this invite was not found or expired"
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:99
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_settings_controller.ex:99
msgid "Unable to delete user"
msgstr ""
#: lib/memex_web/controllers/user_confirmation_controller.ex:54
#, elixir-autogen, elixir-format
#: lib/memex_web/views/error_view.ex:10
msgid "Unauthorized"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_confirmation_controller.ex:54
msgid "User confirmation link is invalid or it has expired."
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:18
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/index.ex:18
msgid "You are not authorized to view this page"
msgstr ""
#: lib/memex_web/controllers/user_auth.ex:177
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_auth.ex:177
msgid "You are not authorized to view this page."
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_auth.ex:39
#: lib/memex_web/controllers/user_auth.ex:161
#, elixir-autogen, elixir-format
msgid "You must confirm your account and log in to access this page."
msgstr ""
#: lib/memex/accounts/user.ex:129
#, elixir-autogen, elixir-format
#: lib/memex/accounts/user.ex:130
msgid "did not change"
msgstr ""
#: lib/memex/accounts/user.ex:150
#, elixir-autogen, elixir-format
#: lib/memex/accounts/user.ex:151
msgid "does not match password"
msgstr ""
#: lib/memex/accounts/user.ex:187
#, elixir-autogen, elixir-format
#: lib/memex/accounts/user.ex:188
msgid "is not valid"
msgstr ""
#: lib/memex/accounts/user.ex:85
#, elixir-autogen, elixir-format
#: lib/memex/accounts/user.ex:84
msgid "must have the @ sign and no spaces"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:21
#: lib/memex_web/templates/user_settings/edit.html.heex:119
#, elixir-autogen, elixir-format
msgid "oops, something went wrong! Please check the errors below"
msgstr ""
#: lib/memex/contexts/context.ex:49
#: lib/memex/contexts/context.ex:62
#: lib/memex/notes/note.ex:48
#: lib/memex/notes/note.ex:61
#: lib/memex/pipelines/pipeline.ex:50
#: lib/memex/pipelines/pipeline.ex:63
#, elixir-autogen, elixir-format
msgid "invalid format: only numbers, letters and hyphen are accepted"
msgstr ""
#: lib/memex_web/templates/error/error.html.heex:28
#, elixir-autogen, elixir-format
msgid "go back home"
msgstr ""
#: lib/memex_web/views/error_view.ex:11
#, elixir-autogen, elixir-format
msgid "internal server error"
msgstr ""
#: lib/memex_web/views/error_view.ex:9
#, elixir-autogen, elixir-format
msgid "not found"
msgstr ""
#: lib/memex_web/views/error_view.ex:10
#, elixir-autogen, elixir-format
msgid "unauthorized"
msgstr ""
#: lib/memex/contexts/context.ex:75
#: lib/memex/notes/note.ex:74
#: lib/memex/pipelines/pipeline.ex:76
#, elixir-autogen, elixir-format
msgid "invalid format: only numbers, letters and hyphen are accepted. tags must be comma-delimited"
msgstr ""

View File

@ -10,149 +10,138 @@
msgid ""
msgstr ""
#: lib/memex_web/controllers/user_confirmation_controller.ex:38
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_confirmation_controller.ex:38
msgid "%{email} confirmed successfully."
msgstr ""
#: lib/memex_web/live/invite_live/form_component.ex:62
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/form_component.ex:62
msgid "%{invite_name} created successfully"
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:53
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/index.ex:53
msgid "%{invite_name} deleted succesfully"
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:114
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/index.ex:114
msgid "%{invite_name} disabled succesfully"
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:90
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/index.ex:90
msgid "%{invite_name} enabled succesfully"
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:68
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/index.ex:68
msgid "%{invite_name} updated succesfully"
msgstr ""
#: lib/memex_web/live/invite_live/form_component.ex:42
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/form_component.ex:42
msgid "%{invite_name} updated successfully"
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:139
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/index.ex:139
msgid "%{user_email} deleted succesfully"
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:29
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_settings_controller.ex:29
msgid "A link to confirm your email change has been sent to the new address."
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:127
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/user_settings/edit.html.heex:133
msgid "Are you sure you want to change your language?"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/index.html.heex:101
#: lib/memex_web/live/invite_live/index.html.heex:130
msgid "Are you sure you want to delete %{email}? This action is permanent!"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/index.html.heex:48
msgid "Are you sure you want to delete the invite for %{invite_name}?"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/templates/user_settings/edit.html.heex:143
msgid "Are you sure you want to delete your account?"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/components/topbar.ex:95
msgid "Are you sure you want to log out?"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/index.html.heex:73
msgid "Are you sure you want to make %{invite_name} unlimited?"
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/index.ex:127
msgid "Copied to clipboard"
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:77
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_settings_controller.ex:77
msgid "Email changed successfully."
msgstr ""
#: lib/memex_web/controllers/user_confirmation_controller.ex:23
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_confirmation_controller.ex:23
msgid "If your email is in our system and it has not been confirmed yet, you will receive an email with instructions shortly."
msgstr ""
#: lib/memex_web/controllers/user_reset_password_controller.ex:24
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_reset_password_controller.ex:24
msgid "If your email is in our system, you will receive instructions to reset your password shortly."
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:65
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_settings_controller.ex:65
msgid "Language updated successfully."
msgstr ""
#: lib/memex_web/controllers/user_reset_password_controller.ex:46
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_session_controller.ex:23
msgid "Logged out successfully."
msgstr ""
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_reset_password_controller.ex:46
msgid "Password reset successfully."
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:49
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_settings_controller.ex:49
msgid "Password updated successfully."
msgstr ""
#: lib/memex_web/controllers/user_registration_controller.ex:73
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_registration_controller.ex:74
msgid "Please check your email to verify your account"
msgstr ""
#: lib/memex_web/live/invite_live/form_component.html.heex:30
#, elixir-autogen, elixir-format
#: lib/memex_web/live/invite_live/form_component.html.heex:30
msgid "Saving..."
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:95
#, elixir-autogen, elixir-format
#: lib/memex_web/controllers/user_settings_controller.ex:95
msgid "Your account has been deleted"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:133
#, elixir-autogen, elixir-format
msgid "are you sure you want to change your language?"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:102
#: lib/memex_web/live/invite_live/index.html.heex:132
#, elixir-autogen, elixir-format
msgid "are you sure you want to delete %{email}? This action is permanent!"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:48
#, elixir-autogen, elixir-format
msgid "are you sure you want to delete the invite for %{invite_name}?"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:143
#, elixir-autogen, elixir-format
msgid "are you sure you want to delete your account?"
msgstr ""
#: lib/memex_web/components/topbar.ex:92
#, elixir-autogen, elixir-format
msgid "are you sure you want to log out?"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:74
#, elixir-autogen, elixir-format
msgid "are you sure you want to make %{invite_name} unlimited?"
msgstr ""
#: lib/memex_web/live/context_live/index.html.heex:46
#: lib/memex_web/live/context_live/show.html.heex:37
#: lib/memex_web/live/note_live/index.html.heex:46
#: lib/memex_web/live/note_live/show.html.heex:34
#: lib/memex_web/live/pipeline_live/index.html.heex:46
#: lib/memex_web/live/pipeline_live/show.html.heex:46
#: lib/memex_web/live/pipeline_live/show.html.heex:116
#, elixir-autogen, elixir-format
msgid "are you sure?"
msgstr ""
#: lib/memex_web/live/home_live.html.heex:95
#, elixir-autogen, elixir-format
msgid "register to setup %{name}"
msgstr ""
#: lib/memex_web/controllers/user_session_controller.ex:23
#, elixir-autogen, elixir-format
msgid "logged out successfully."
#: lib/memex_web/live/home_live.html.heex:91
msgid "Register to setup %{name}"
msgstr ""

View File

@ -6,30 +6,10 @@ defmodule Memex.Repo.Migrations.CreateContexts do
add :id, :binary_id, primary_key: true
add :title, :string
add :content, :text
add :tags, {:array, :string}
add :tag, {:array, :string}
add :visibility, :string
add :user_id, references(:users, on_delete: :delete_all, type: :binary_id)
timestamps()
end
flush()
execute """
ALTER TABLE contexts
ADD COLUMN search tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('english', coalesce(title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(immutable_array_to_string(tags, ' '), '')), 'B') ||
setweight(to_tsvector('english', coalesce(content, '')), 'C')
) STORED
"""
execute("CREATE INDEX contexts_trgm_idx ON contexts USING GIN (search)")
end
def down do
drop table(:contexts)
end
end

View File

@ -6,30 +6,9 @@ defmodule Memex.Repo.Migrations.CreatePipelines do
add :id, :binary_id, primary_key: true
add :title, :string
add :description, :text
add :tags, {:array, :citext}
add :visibility, :string
add :user_id, references(:users, on_delete: :delete_all, type: :binary_id)
timestamps()
end
flush()
execute """
ALTER TABLE pipelines
ADD COLUMN search tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('english', coalesce(title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(immutable_array_to_string(tags, ' '), '')), 'B') ||
setweight(to_tsvector('english', coalesce(description, '')), 'C')
) STORED
"""
execute("CREATE INDEX pipelines_trgm_idx ON pipelines USING GIN (search)")
end
def down do
drop table(:pipelines)
end
end

View File

@ -5,16 +5,13 @@ defmodule Memex.Repo.Migrations.CreateSteps do
create table(:steps, primary_key: false) do
add :id, :binary_id, primary_key: true
add :title, :string
add :content, :text
add :description, :text
add :position, :integer
add :pipeline_id, references(:pipelines, on_delete: :nothing, type: :binary_id)
add :user_id, references(:users, on_delete: :nothing, type: :binary_id)
timestamps()
end
create index(:steps, [:pipeline_id])
create index(:steps, [:user_id])
end
end

View File

@ -0,0 +1,16 @@
defmodule Memex.Repo.Migrations.CreateStepContexts do
use Ecto.Migration
def change do
create table(:step_contexts, primary_key: false) do
add :id, :binary_id, primary_key: true
add :step_id, references(:steps, on_delete: :nothing, type: :binary_id)
add :context_id, references(:contexts, on_delete: :nothing, type: :binary_id)
timestamps()
end
create index(:step_contexts, [:step_id])
create index(:step_contexts, [:context_id])
end
end

View File

@ -0,0 +1,16 @@
defmodule Memex.Repo.Migrations.CreateContextNotes do
use Ecto.Migration
def change do
create table(:context_notes, primary_key: false) do
add :id, :binary_id, primary_key: true
add :context_id, references(:contexts, on_delete: :nothing, type: :binary_id)
add :note_id, references(:notes, on_delete: :nothing, type: :binary_id)
timestamps()
end
create index(:context_notes, [:context_id])
create index(:context_notes, [:note_id])
end
end

View File

@ -1,14 +0,0 @@
defmodule Memex.Repo.Migrations.UseSlugs do
use Ecto.Migration
def change do
rename table(:notes), :title, to: :slug
create unique_index(:notes, [:slug])
rename table(:contexts), :title, to: :slug
create unique_index(:contexts, [:slug])
rename table(:pipelines), :title, to: :slug
create unique_index(:pipelines, [:slug])
end
end

View File

@ -1,56 +0,0 @@
defmodule Memex.Repo.Migrations.FixSearch do
use Ecto.Migration
def up do
reset_search_columns()
end
def down do
# no way to rollback this migration since the previous generated search columns were invalid
reset_search_columns()
end
defp reset_search_columns() do
alter table(:notes), do: remove(:search)
alter table(:contexts), do: remove(:search)
alter table(:pipelines), do: remove(:search)
flush()
execute """
ALTER TABLE notes
ADD COLUMN search tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('english', coalesce(slug, '')), 'A') ||
setweight(to_tsvector('english', coalesce(immutable_array_to_string(tags, ' '), '')), 'B') ||
setweight(to_tsvector('english', coalesce(content, '')), 'C')
) STORED
"""
execute("CREATE INDEX notes_trgm_idx ON notes USING GIN (search)")
execute """
ALTER TABLE contexts
ADD COLUMN search tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('english', coalesce(slug, '')), 'A') ||
setweight(to_tsvector('english', coalesce(immutable_array_to_string(tags, ' '), '')), 'B') ||
setweight(to_tsvector('english', coalesce(content, '')), 'C')
) STORED
"""
execute("CREATE INDEX contexts_trgm_idx ON contexts USING GIN (search)")
execute """
ALTER TABLE pipelines
ADD COLUMN search tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('english', coalesce(slug, '')), 'A') ||
setweight(to_tsvector('english', coalesce(immutable_array_to_string(tags, ' '), '')), 'B') ||
setweight(to_tsvector('english', coalesce(description, '')), 'C')
) STORED
"""
execute("CREATE INDEX pipelines_trgm_idx ON pipelines USING GIN (search)")
end
end

View File

@ -1,8 +1,6 @@
# memEx
# Memex
![old screenshot](https://gitea.bubbletea.dev/shibao/memEx/raw/branch/stable/home.png)
memEx is an easy way to digitize the structured processes of your life.
memex is an easy way to digitize the structured processes of your life.
- Notes: Document notes about individual items or concepts
- Contexts: Provide context around a single topic and hotlink to individual
@ -18,7 +16,7 @@ memEx is an easy way to digitize the structured processes of your life.
# Installation
1. Install [Docker Compose](https://docs.docker.com/compose/install/) or alternatively [Docker Desktop](https://docs.docker.com/desktop/) on your machine.
1. Copy the example [docker-compose.yml](https://gitea.bubbletea.dev/shibao/memEx/src/branch/stable/docker-compose.yml). into your local machine where you want.
1. Copy the example [docker-compose.yml](https://gitea.bubbletea.dev/shibao/memex/src/branch/stable/docker-compose.yml). into your local machine where you want.
Bind mounts are created in the same directory by default.
1. Set the configuration variables in `docker-compose.yml`. You'll need to run
`docker run -it shibaobun/memex /app/priv/random.sh` to generate a new
@ -29,8 +27,8 @@ The first created user will be created as an admin.
# Configuration
You can use the following environment variables to configure memEx in
[docker-compose.yml](https://gitea.bubbletea.dev/shibao/memEx/src/branch/stable/docker-compose.yml).
You can use the following environment variables to configure Memex in
[docker-compose.yml](https://gitea.bubbletea.dev/shibao/memex/src/branch/stable/docker-compose.yml).
- `HOST`: External url to generate links with. Must be set with your hosted
domain name! I.e. `memex.mywebsite.tld`
@ -52,11 +50,9 @@ You can use the following environment variables to configure memEx in
- `SMTP_SSL`: Set to `true` to enable SSL for emails. Defaults to `false`.
- `EMAIL_FROM`: Sets the sender email in sent emails. Defaults to
`no-reply@HOST` where `HOST` was previously defined.
- `EMAIL_NAME`: Sets the sender name in sent emails. Defaults to "memEx".
- `EMAIL_NAME`: Sets the sender name in sent emails. Defaults to "Memex".
---
[![Build
Status](https://drone.bubbletea.dev/api/badges/shibao/memEx/status.svg?ref=refs/heads/dev)](https://drone.bubbletea.dev/shibao/memEx)
[![translation
status](https://weblate.bubbletea.dev/widgets/memEx/-/svg-badge.svg)](https://weblate.bubbletea.dev/engage/memEx/)
Status](https://drone.bubbletea.dev/api/badges/shibao/memex/status.svg?ref=refs/heads/dev)](https://drone.bubbletea.dev/shibao/memex)

View File

@ -104,7 +104,7 @@ defmodule Memex.AccountsTest do
describe "change_user_registration/2" do
test "returns a changeset" do
assert %Changeset{} = changeset = Accounts.change_user_registration()
assert %Changeset{} = changeset = Accounts.change_user_registration(%User{})
assert changeset.required == [:password, :email]
end
@ -112,7 +112,8 @@ defmodule Memex.AccountsTest do
email = unique_user_email()
password = valid_user_password()
changeset = Accounts.change_user_registration(%{"email" => email, "password" => password})
changeset =
Accounts.change_user_registration(%User{}, %{"email" => email, "password" => password})
assert changeset.valid?
assert get_change(changeset, :email) == email

View File

@ -1,213 +1,71 @@
defmodule Memex.ContextsTest do
use Memex.DataCase
import Memex.ContextsFixtures
alias Memex.{Contexts, Contexts.Context}
@moduletag :contexts_test
@invalid_attrs %{content: nil, tag: nil, slug: nil, visibility: nil}
alias Memex.Contexts
describe "contexts" do
setup do
[user: user_fixture()]
alias Memex.Contexts.Context
import Memex.ContextsFixtures
@invalid_attrs %{content: nil, tag: nil, title: nil, visibility: nil}
test "list_contexts/0 returns all contexts" do
context = context_fixture()
assert Contexts.list_contexts() == [context]
end
test "list_contexts/1 returns all contexts for a user", %{user: user} do
context_a = context_fixture(%{slug: "a", visibility: :public}, user)
context_b = context_fixture(%{slug: "b", visibility: :unlisted}, user)
context_c = context_fixture(%{slug: "c", visibility: :private}, user)
assert Contexts.list_contexts(user) == [context_a, context_b, context_c]
test "get_context!/1 returns the context with given id" do
context = context_fixture()
assert Contexts.get_context!(context.id) == context
end
test "list_contexts/2 returns relevant contexts for a user", %{user: user} do
context_a = context_fixture(%{slug: "dogs", content: "has some treats in it"}, user)
context_b = context_fixture(%{slug: "cats", tags: ["home"]}, user)
test "create_context/1 with valid data creates a context" do
valid_attrs = %{content: "some content", tag: [], title: "some title", visibility: :public}
context_c =
%{slug: "chickens", content: "bananas stuff", tags: ["life", "decisions"]}
|> context_fixture(user)
_shouldnt_return =
%{slug: "dog", content: "banana treat stuff", visibility: :private}
|> context_fixture(user_fixture())
# slug
assert Contexts.list_contexts("dog", user) == [context_a]
assert Contexts.list_contexts("dogs", user) == [context_a]
assert Contexts.list_contexts("cat", user) == [context_b]
assert Contexts.list_contexts("chicken", user) == [context_c]
# content
assert Contexts.list_contexts("treat", user) == [context_a]
assert Contexts.list_contexts("banana", user) == [context_c]
assert Contexts.list_contexts("stuff", user) == [context_c]
# tag
assert Contexts.list_contexts("home", user) == [context_b]
assert Contexts.list_contexts("life", user) == [context_c]
assert Contexts.list_contexts("decision", user) == [context_c]
assert Contexts.list_contexts("decisions", user) == [context_c]
end
test "list_public_contexts/0 returns public contexts", %{user: user} do
public_context = context_fixture(%{visibility: :public}, user)
context_fixture(%{visibility: :unlisted}, user)
context_fixture(%{visibility: :private}, user)
assert Contexts.list_public_contexts() == [public_context]
end
test "list_public_contexts/1 returns relevant contexts for a user", %{user: user} do
context_a =
%{slug: "dogs", content: "has some treats in it", visibility: :public}
|> context_fixture(user)
context_b =
%{slug: "cats", tags: ["home"], visibility: :public}
|> context_fixture(user)
context_c =
%{
slug: "chickens",
content: "bananas stuff",
tags: ["life", "decisions"],
visibility: :public
}
|> context_fixture(user)
_shouldnt_return =
%{
slug: "dog",
content: "treats bananas stuff",
tags: ["home", "life", "decisions"],
visibility: :private
}
|> context_fixture(user)
# slug
assert Contexts.list_public_contexts("dog") == [context_a]
assert Contexts.list_public_contexts("dogs") == [context_a]
assert Contexts.list_public_contexts("cat") == [context_b]
assert Contexts.list_public_contexts("chicken") == [context_c]
# content
assert Contexts.list_public_contexts("treat") == [context_a]
assert Contexts.list_public_contexts("banana") == [context_c]
assert Contexts.list_public_contexts("stuff") == [context_c]
# tag
assert Contexts.list_public_contexts("home") == [context_b]
assert Contexts.list_public_contexts("life") == [context_c]
assert Contexts.list_public_contexts("decision") == [context_c]
assert Contexts.list_public_contexts("decisions") == [context_c]
end
test "get_context!/1 returns the context with given id", %{user: user} do
context = context_fixture(%{visibility: :public}, user)
assert Contexts.get_context!(context.id, user) == context
context = context_fixture(%{visibility: :unlisted}, user)
assert Contexts.get_context!(context.id, user) == context
context = context_fixture(%{visibility: :private}, user)
assert Contexts.get_context!(context.id, user) == context
end
test "get_context!/1 only returns unlisted or public contexts for other users", %{user: user} do
another_user = user_fixture()
context = context_fixture(%{visibility: :public}, another_user)
assert Contexts.get_context!(context.id, user) == context
context = context_fixture(%{visibility: :unlisted}, another_user)
assert Contexts.get_context!(context.id, user) == context
context = context_fixture(%{visibility: :private}, another_user)
assert_raise Ecto.NoResultsError, fn ->
Contexts.get_context!(context.id, user)
end
end
test "get_context_by_slug/1 returns the context with given id", %{user: user} do
context = context_fixture(%{slug: "a", visibility: :public}, user)
assert Contexts.get_context_by_slug("a", user) == context
context = context_fixture(%{slug: "b", visibility: :unlisted}, user)
assert Contexts.get_context_by_slug("b", user) == context
context = context_fixture(%{slug: "c", visibility: :private}, user)
assert Contexts.get_context_by_slug("c", user) == context
end
test "get_context_by_slug/1 only returns unlisted or public contexts for other users", %{
user: user
} do
another_user = user_fixture()
context = context_fixture(%{slug: "a", visibility: :public}, another_user)
assert Contexts.get_context_by_slug("a", user) == context
context = context_fixture(%{slug: "b", visibility: :unlisted}, another_user)
assert Contexts.get_context_by_slug("b", user) == context
context_fixture(%{slug: "c", visibility: :private}, another_user)
assert Contexts.get_context_by_slug("c", user) |> is_nil()
end
test "create_context/1 with valid data creates a context", %{user: user} do
valid_attrs = %{
"content" => "some content",
"tags_string" => "tag1,tag2",
"slug" => "some-slug",
"visibility" => :public
}
assert {:ok, %Context{} = context} = Contexts.create_context(valid_attrs, user)
assert {:ok, %Context{} = context} = Contexts.create_context(valid_attrs)
assert context.content == "some content"
assert context.tags == ["tag1", "tag2"]
assert context.slug == "some-slug"
assert context.tag == []
assert context.title == "some title"
assert context.visibility == :public
end
test "create_context/1 with invalid data returns error changeset", %{user: user} do
assert {:error, %Ecto.Changeset{}} = Contexts.create_context(@invalid_attrs, user)
test "create_context/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Contexts.create_context(@invalid_attrs)
end
test "update_context/2 with valid data updates the context", %{user: user} do
context = context_fixture(user)
test "update_context/2 with valid data updates the context" do
context = context_fixture()
update_attrs = %{
"content" => "some updated content",
"tags_string" => "tag1,tag2",
"slug" => "some-updated-slug",
"visibility" => :private
content: "some updated content",
tag: [],
title: "some updated title",
visibility: :private
}
assert {:ok, %Context{} = context} = Contexts.update_context(context, update_attrs, user)
assert {:ok, %Context{} = context} = Contexts.update_context(context, update_attrs)
assert context.content == "some updated content"
assert context.tags == ["tag1", "tag2"]
assert context.slug == "some-updated-slug"
assert context.tag == []
assert context.title == "some updated title"
assert context.visibility == :private
end
test "update_context/2 with invalid data returns error changeset", %{user: user} do
context = context_fixture(user)
assert {:error, %Ecto.Changeset{}} = Contexts.update_context(context, @invalid_attrs, user)
assert context == Contexts.get_context!(context.id, user)
test "update_context/2 with invalid data returns error changeset" do
context = context_fixture()
assert {:error, %Ecto.Changeset{}} = Contexts.update_context(context, @invalid_attrs)
assert context == Contexts.get_context!(context.id)
end
test "delete_context/1 deletes the context", %{user: user} do
context = context_fixture(user)
assert {:ok, %Context{}} = Contexts.delete_context(context, user)
assert_raise Ecto.NoResultsError, fn -> Contexts.get_context!(context.id, user) end
test "delete_context/1 deletes the context" do
context = context_fixture()
assert {:ok, %Context{}} = Contexts.delete_context(context)
assert_raise Ecto.NoResultsError, fn -> Contexts.get_context!(context.id) end
end
test "delete_context/1 deletes the context for an admin user", %{user: user} do
admin_user = admin_fixture()
context = context_fixture(user)
assert {:ok, %Context{}} = Contexts.delete_context(context, admin_user)
assert_raise Ecto.NoResultsError, fn -> Contexts.get_context!(context.id, user) end
end
test "change_context/1 returns a context changeset", %{user: user} do
context = context_fixture(user)
assert %Ecto.Changeset{} = Contexts.change_context(context, user)
test "change_context/1 returns a context changeset" do
context = context_fixture()
assert %Ecto.Changeset{} = Contexts.change_context(context)
end
end
end

View File

@ -2,8 +2,8 @@ defmodule Memex.NotesTest do
use Memex.DataCase
import Memex.NotesFixtures
alias Memex.{Notes, Notes.Note}
@moduletag :notes_test
@invalid_attrs %{content: nil, tag: nil, slug: nil, visibility: nil}
@invalid_attrs %{content: nil, tag: nil, title: nil, visibility: nil}
describe "notes" do
setup do
@ -11,44 +11,12 @@ defmodule Memex.NotesTest do
end
test "list_notes/1 returns all notes for a user", %{user: user} do
note_a = note_fixture(%{slug: "a", visibility: :public}, user)
note_b = note_fixture(%{slug: "b", visibility: :unlisted}, user)
note_c = note_fixture(%{slug: "c", visibility: :private}, user)
_shouldnt_return = note_fixture(%{visibility: :private}, user_fixture())
note_a = note_fixture(%{title: "a", visibility: :public}, user)
note_b = note_fixture(%{title: "b", visibility: :unlisted}, user)
note_c = note_fixture(%{title: "c", visibility: :private}, user)
assert Notes.list_notes(user) == [note_a, note_b, note_c]
end
test "list_notes/2 returns relevant notes for a user", %{user: user} do
note_a = note_fixture(%{slug: "dogs", content: "has some treats in it"}, user)
note_b = note_fixture(%{slug: "cats", tags: ["home"]}, user)
note_c =
%{slug: "chickens", content: "bananas stuff", tags: ["life", "decisions"]}
|> note_fixture(user)
_shouldnt_return =
%{slug: "dog", content: "banana treat stuff", visibility: :private}
|> note_fixture(user_fixture())
# slug
assert Notes.list_notes("dog", user) == [note_a]
assert Notes.list_notes("dogs", user) == [note_a]
assert Notes.list_notes("cat", user) == [note_b]
assert Notes.list_notes("chicken", user) == [note_c]
# content
assert Notes.list_notes("treat", user) == [note_a]
assert Notes.list_notes("banana", user) == [note_c]
assert Notes.list_notes("stuff", user) == [note_c]
# tag
assert Notes.list_notes("home", user) == [note_b]
assert Notes.list_notes("life", user) == [note_c]
assert Notes.list_notes("decision", user) == [note_c]
assert Notes.list_notes("decisions", user) == [note_c]
end
test "list_public_notes/0 returns public notes", %{user: user} do
public_note = note_fixture(%{visibility: :public}, user)
note_fixture(%{visibility: :unlisted}, user)
@ -56,114 +24,23 @@ defmodule Memex.NotesTest do
assert Notes.list_public_notes() == [public_note]
end
test "list_public_notes/1 returns relevant notes for a user", %{user: user} do
note_a =
%{slug: "dogs", content: "has some treats in it", visibility: :public}
|> note_fixture(user)
note_b =
%{slug: "cats", tags: ["home"], visibility: :public}
|> note_fixture(user)
note_c =
%{
slug: "chickens",
content: "bananas stuff",
tags: ["life", "decisions"],
visibility: :public
}
|> note_fixture(user)
_shouldnt_return =
%{
slug: "dog",
content: "treats bananas stuff",
tags: ["home", "life", "decisions"],
visibility: :private
}
|> note_fixture(user)
# slug
assert Notes.list_public_notes("dog") == [note_a]
assert Notes.list_public_notes("dogs") == [note_a]
assert Notes.list_public_notes("cat") == [note_b]
assert Notes.list_public_notes("chicken") == [note_c]
# content
assert Notes.list_public_notes("treat") == [note_a]
assert Notes.list_public_notes("banana") == [note_c]
assert Notes.list_public_notes("stuff") == [note_c]
# tag
assert Notes.list_public_notes("home") == [note_b]
assert Notes.list_public_notes("life") == [note_c]
assert Notes.list_public_notes("decision") == [note_c]
assert Notes.list_public_notes("decisions") == [note_c]
end
test "get_note!/1 returns the note with given id", %{user: user} do
note = note_fixture(%{visibility: :public}, user)
note = note_fixture(user)
assert Notes.get_note!(note.id, user) == note
note = note_fixture(%{visibility: :unlisted}, user)
assert Notes.get_note!(note.id, user) == note
note = note_fixture(%{visibility: :private}, user)
assert Notes.get_note!(note.id, user) == note
end
test "get_note!/1 only returns unlisted or public notes for other users", %{user: user} do
another_user = user_fixture()
note = note_fixture(%{visibility: :public}, another_user)
assert Notes.get_note!(note.id, user) == note
note = note_fixture(%{visibility: :unlisted}, another_user)
assert Notes.get_note!(note.id, user) == note
note = note_fixture(%{visibility: :private}, another_user)
assert_raise Ecto.NoResultsError, fn ->
Notes.get_note!(note.id, user)
end
end
test "get_note_by_slug/1 returns the note with given id", %{user: user} do
note = note_fixture(%{slug: "a", visibility: :public}, user)
assert Notes.get_note_by_slug("a", user) == note
note = note_fixture(%{slug: "b", visibility: :unlisted}, user)
assert Notes.get_note_by_slug("b", user) == note
note = note_fixture(%{slug: "c", visibility: :private}, user)
assert Notes.get_note_by_slug("c", user) == note
end
test "get_note_by_slug/1 only returns unlisted or public notes for other users", %{
user: user
} do
another_user = user_fixture()
note = note_fixture(%{slug: "a", visibility: :public}, another_user)
assert Notes.get_note_by_slug("a", user) == note
note = note_fixture(%{slug: "b", visibility: :unlisted}, another_user)
assert Notes.get_note_by_slug("b", user) == note
note_fixture(%{slug: "c", visibility: :private}, another_user)
assert Notes.get_note_by_slug("c", user) |> is_nil()
end
test "create_note/1 with valid data creates a note", %{user: user} do
valid_attrs = %{
"content" => "some content",
"tags_string" => "tag1,tag2",
"slug" => "some-slug",
"title" => "some title",
"visibility" => :public
}
assert {:ok, %Note{} = note} = Notes.create_note(valid_attrs, user)
assert note.content == "some content"
assert note.tags == ["tag1", "tag2"]
assert note.slug == "some-slug"
assert note.title == "some title"
assert note.visibility == :public
end
@ -177,14 +54,14 @@ defmodule Memex.NotesTest do
update_attrs = %{
"content" => "some updated content",
"tags_string" => "tag1,tag2",
"slug" => "some-updated-slug",
"title" => "some updated title",
"visibility" => :private
}
assert {:ok, %Note{} = note} = Notes.update_note(note, update_attrs, user)
assert note.content == "some updated content"
assert note.tags == ["tag1", "tag2"]
assert note.slug == "some-updated-slug"
assert note.title == "some updated title"
assert note.visibility == :private
end
@ -200,13 +77,6 @@ defmodule Memex.NotesTest do
assert_raise Ecto.NoResultsError, fn -> Notes.get_note!(note.id, user) end
end
test "delete_note/1 deletes the note for an admin user", %{user: user} do
admin_user = admin_fixture()
note = note_fixture(user)
assert {:ok, %Note{}} = Notes.delete_note(note, admin_user)
assert_raise Ecto.NoResultsError, fn -> Notes.get_note!(note.id, user) end
end
test "change_note/1 returns a note changeset", %{user: user} do
note = note_fixture(user)
assert %Ecto.Changeset{} = Notes.change_note(note, user)

View File

@ -1,220 +1,68 @@
defmodule Memex.PipelinesTest do
use Memex.DataCase
import Memex.PipelinesFixtures
alias Memex.{Pipelines, Pipelines.Pipeline}
@moduletag :pipelines_test
@invalid_attrs %{description: nil, tag: nil, slug: nil, visibility: nil}
alias Memex.Pipelines
describe "pipelines" do
setup do
[user: user_fixture()]
alias Memex.Pipelines.Pipeline
import Memex.PipelinesFixtures
@invalid_attrs %{description: nil, title: nil, visibility: nil}
test "list_pipelines/0 returns all pipelines" do
pipeline = pipeline_fixture()
assert Pipelines.list_pipelines() == [pipeline]
end
test "list_pipelines/1 returns all pipelines for a user", %{user: user} do
pipeline_a = pipeline_fixture(%{slug: "a", visibility: :public}, user)
pipeline_b = pipeline_fixture(%{slug: "b", visibility: :unlisted}, user)
pipeline_c = pipeline_fixture(%{slug: "c", visibility: :private}, user)
assert Pipelines.list_pipelines(user) == [pipeline_a, pipeline_b, pipeline_c]
test "get_pipeline!/1 returns the pipeline with given id" do
pipeline = pipeline_fixture()
assert Pipelines.get_pipeline!(pipeline.id) == pipeline
end
test "list_pipelines/2 returns relevant pipelines for a user", %{user: user} do
pipeline_a = pipeline_fixture(%{slug: "dogs", description: "has some treats in it"}, user)
pipeline_b = pipeline_fixture(%{slug: "cats", tags: ["home"]}, user)
test "create_pipeline/1 with valid data creates a pipeline" do
valid_attrs = %{description: "some description", title: "some title", visibility: :public}
pipeline_c =
%{slug: "chickens", description: "bananas stuff", tags: ["life", "decisions"]}
|> pipeline_fixture(user)
_shouldnt_return =
%{slug: "dog", description: "banana treat stuff", visibility: :private}
|> pipeline_fixture(user_fixture())
# slug
assert Pipelines.list_pipelines("dog", user) == [pipeline_a]
assert Pipelines.list_pipelines("dogs", user) == [pipeline_a]
assert Pipelines.list_pipelines("cat", user) == [pipeline_b]
assert Pipelines.list_pipelines("chicken", user) == [pipeline_c]
# description
assert Pipelines.list_pipelines("treat", user) == [pipeline_a]
assert Pipelines.list_pipelines("banana", user) == [pipeline_c]
assert Pipelines.list_pipelines("stuff", user) == [pipeline_c]
# tag
assert Pipelines.list_pipelines("home", user) == [pipeline_b]
assert Pipelines.list_pipelines("life", user) == [pipeline_c]
assert Pipelines.list_pipelines("decision", user) == [pipeline_c]
assert Pipelines.list_pipelines("decisions", user) == [pipeline_c]
end
test "list_public_pipelines/0 returns public pipelines", %{user: user} do
public_pipeline = pipeline_fixture(%{visibility: :public}, user)
pipeline_fixture(%{visibility: :unlisted}, user)
pipeline_fixture(%{visibility: :private}, user)
assert Pipelines.list_public_pipelines() == [public_pipeline]
end
test "list_public_pipelines/1 returns relevant pipelines for a user", %{user: user} do
pipeline_a =
%{slug: "dogs", description: "has some treats in it", visibility: :public}
|> pipeline_fixture(user)
pipeline_b =
%{slug: "cats", tags: ["home"], visibility: :public}
|> pipeline_fixture(user)
pipeline_c =
%{
slug: "chickens",
description: "bananas stuff",
tags: ["life", "decisions"],
visibility: :public
}
|> pipeline_fixture(user)
_shouldnt_return =
%{
slug: "dog",
description: "treats bananas stuff",
tags: ["home", "life", "decisions"],
visibility: :private
}
|> pipeline_fixture(user)
# slug
assert Pipelines.list_public_pipelines("dog") == [pipeline_a]
assert Pipelines.list_public_pipelines("dogs") == [pipeline_a]
assert Pipelines.list_public_pipelines("cat") == [pipeline_b]
assert Pipelines.list_public_pipelines("chicken") == [pipeline_c]
# description
assert Pipelines.list_public_pipelines("treat") == [pipeline_a]
assert Pipelines.list_public_pipelines("banana") == [pipeline_c]
assert Pipelines.list_public_pipelines("stuff") == [pipeline_c]
# tag
assert Pipelines.list_public_pipelines("home") == [pipeline_b]
assert Pipelines.list_public_pipelines("life") == [pipeline_c]
assert Pipelines.list_public_pipelines("decision") == [pipeline_c]
assert Pipelines.list_public_pipelines("decisions") == [pipeline_c]
end
test "get_pipeline!/1 returns the pipeline with given id", %{user: user} do
pipeline = pipeline_fixture(%{visibility: :public}, user)
assert Pipelines.get_pipeline!(pipeline.id, user) == pipeline
pipeline = pipeline_fixture(%{visibility: :unlisted}, user)
assert Pipelines.get_pipeline!(pipeline.id, user) == pipeline
pipeline = pipeline_fixture(%{visibility: :private}, user)
assert Pipelines.get_pipeline!(pipeline.id, user) == pipeline
end
test "get_pipeline!/1 only returns unlisted or public pipelines for other users", %{
user: user
} do
another_user = user_fixture()
pipeline = pipeline_fixture(%{visibility: :public}, another_user)
assert Pipelines.get_pipeline!(pipeline.id, user) == pipeline
pipeline = pipeline_fixture(%{visibility: :unlisted}, another_user)
assert Pipelines.get_pipeline!(pipeline.id, user) == pipeline
pipeline = pipeline_fixture(%{visibility: :private}, another_user)
assert_raise Ecto.NoResultsError, fn ->
Pipelines.get_pipeline!(pipeline.id, user)
end
end
test "get_pipeline_by_slug/1 returns the pipeline with given id", %{user: user} do
pipeline = pipeline_fixture(%{slug: "a", visibility: :public}, user)
assert Pipelines.get_pipeline_by_slug("a", user) == pipeline
pipeline = pipeline_fixture(%{slug: "b", visibility: :unlisted}, user)
assert Pipelines.get_pipeline_by_slug("b", user) == pipeline
pipeline = pipeline_fixture(%{slug: "c", visibility: :private}, user)
assert Pipelines.get_pipeline_by_slug("c", user) == pipeline
end
test "get_pipeline_by_slug/1 only returns unlisted or public pipelines for other users", %{
user: user
} do
another_user = user_fixture()
pipeline = pipeline_fixture(%{slug: "a", visibility: :public}, another_user)
assert Pipelines.get_pipeline_by_slug("a", user) == pipeline
pipeline = pipeline_fixture(%{slug: "b", visibility: :unlisted}, another_user)
assert Pipelines.get_pipeline_by_slug("b", user) == pipeline
pipeline_fixture(%{slug: "c", visibility: :private}, another_user)
assert Pipelines.get_pipeline_by_slug("c", user) |> is_nil()
end
test "create_pipeline/1 with valid data creates a pipeline", %{user: user} do
valid_attrs = %{
"description" => "some description",
"tags_string" => "tag1,tag2",
"slug" => "some-slug",
"visibility" => :public
}
assert {:ok, %Pipeline{} = pipeline} = Pipelines.create_pipeline(valid_attrs, user)
assert {:ok, %Pipeline{} = pipeline} = Pipelines.create_pipeline(valid_attrs)
assert pipeline.description == "some description"
assert pipeline.tags == ["tag1", "tag2"]
assert pipeline.slug == "some-slug"
assert pipeline.title == "some title"
assert pipeline.visibility == :public
end
test "create_pipeline/1 with invalid data returns error changeset", %{user: user} do
assert {:error, %Ecto.Changeset{}} = Pipelines.create_pipeline(@invalid_attrs, user)
test "create_pipeline/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Pipelines.create_pipeline(@invalid_attrs)
end
test "update_pipeline/2 with valid data updates the pipeline", %{user: user} do
pipeline = pipeline_fixture(user)
test "update_pipeline/2 with valid data updates the pipeline" do
pipeline = pipeline_fixture()
update_attrs = %{
"description" => "some updated description",
"tags_string" => "tag1,tag2",
"slug" => "some-updated-slug",
"visibility" => :private
description: "some updated description",
title: "some updated title",
visibility: :private
}
assert {:ok, %Pipeline{} = pipeline} =
Pipelines.update_pipeline(pipeline, update_attrs, user)
assert {:ok, %Pipeline{} = pipeline} = Pipelines.update_pipeline(pipeline, update_attrs)
assert pipeline.description == "some updated description"
assert pipeline.tags == ["tag1", "tag2"]
assert pipeline.slug == "some-updated-slug"
assert pipeline.title == "some updated title"
assert pipeline.visibility == :private
end
test "update_pipeline/2 with invalid data returns error changeset", %{user: user} do
pipeline = pipeline_fixture(user)
assert {:error, %Ecto.Changeset{}} =
Pipelines.update_pipeline(pipeline, @invalid_attrs, user)
assert pipeline == Pipelines.get_pipeline!(pipeline.id, user)
test "update_pipeline/2 with invalid data returns error changeset" do
pipeline = pipeline_fixture()
assert {:error, %Ecto.Changeset{}} = Pipelines.update_pipeline(pipeline, @invalid_attrs)
assert pipeline == Pipelines.get_pipeline!(pipeline.id)
end
test "delete_pipeline/1 deletes the pipeline", %{user: user} do
pipeline = pipeline_fixture(user)
assert {:ok, %Pipeline{}} = Pipelines.delete_pipeline(pipeline, user)
assert_raise Ecto.NoResultsError, fn -> Pipelines.get_pipeline!(pipeline.id, user) end
test "delete_pipeline/1 deletes the pipeline" do
pipeline = pipeline_fixture()
assert {:ok, %Pipeline{}} = Pipelines.delete_pipeline(pipeline)
assert_raise Ecto.NoResultsError, fn -> Pipelines.get_pipeline!(pipeline.id) end
end
test "delete_pipeline/1 deletes the pipeline for an admin user", %{user: user} do
admin_user = admin_fixture()
pipeline = pipeline_fixture(user)
assert {:ok, %Pipeline{}} = Pipelines.delete_pipeline(pipeline, admin_user)
assert_raise Ecto.NoResultsError, fn -> Pipelines.get_pipeline!(pipeline.id, user) end
end
test "change_pipeline/1 returns a pipeline changeset", %{user: user} do
pipeline = pipeline_fixture(user)
assert %Ecto.Changeset{} = Pipelines.change_pipeline(pipeline, user)
test "change_pipeline/1 returns a pipeline changeset" do
pipeline = pipeline_fixture()
assert %Ecto.Changeset{} = Pipelines.change_pipeline(pipeline)
end
end
end

View File

@ -1,148 +1,68 @@
defmodule Memex.StepsTest do
use Memex.DataCase
import Memex.{PipelinesFixtures, StepsFixtures}
alias Memex.Pipelines.{Steps, Steps.Step}
@moduletag :steps_test
@invalid_attrs %{content: nil, title: nil}
alias Memex.Steps
describe "steps" do
setup do
user = user_fixture()
pipeline = pipeline_fixture(user)
alias Memex.Steps.Step
[user: user, pipeline: pipeline]
import Memex.StepsFixtures
@invalid_attrs %{description: nil, position: nil, title: nil}
test "list_steps/0 returns all steps" do
step = step_fixture()
assert Steps.list_steps() == [step]
end
test "list_steps/2 returns all steps for a user", %{pipeline: pipeline, user: user} do
step_a = step_fixture(0, pipeline, user)
step_b = step_fixture(1, pipeline, user)
step_c = step_fixture(2, pipeline, user)
assert Steps.list_steps(pipeline, user) == [step_a, step_b, step_c]
test "get_step!/1 returns the step with given id" do
step = step_fixture()
assert Steps.get_step!(step.id) == step
end
test "get_step!/2 returns the step with given id", %{pipeline: pipeline, user: user} do
step = step_fixture(0, pipeline, user)
assert Steps.get_step!(step.id, user) == step
end
test "create_step/1 with valid data creates a step" do
valid_attrs = %{description: "some description", position: 42, title: "some title"}
test "get_step!/2 only returns unlisted or public steps for other users", %{user: user} do
another_user = user_fixture()
another_pipeline = pipeline_fixture(another_user)
step = step_fixture(0, another_pipeline, another_user)
assert_raise Ecto.NoResultsError, fn ->
Steps.get_step!(step.id, user)
end
end
test "create_step/4 with valid data creates a step", %{pipeline: pipeline, user: user} do
valid_attrs = %{
"content" => "some content",
"title" => "some title"
}
assert {:ok, %Step{} = step} = Steps.create_step(valid_attrs, 0, pipeline, user)
assert step.content == "some content"
assert {:ok, %Step{} = step} = Steps.create_step(valid_attrs)
assert step.description == "some description"
assert step.position == 42
assert step.title == "some title"
end
test "create_step/4 with invalid data returns error changeset",
%{pipeline: pipeline, user: user} do
assert {:error, %Ecto.Changeset{}} = Steps.create_step(@invalid_attrs, 0, pipeline, user)
test "create_step/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Steps.create_step(@invalid_attrs)
end
test "update_step/3 with valid data updates the step", %{pipeline: pipeline, user: user} do
step = step_fixture(0, pipeline, user)
test "update_step/2 with valid data updates the step" do
step = step_fixture()
update_attrs = %{
"content" => "some updated content",
"title" => "some updated title"
description: "some updated description",
position: 43,
title: "some updated title"
}
assert {:ok, %Step{} = step} = Steps.update_step(step, update_attrs, user)
assert step.content == "some updated content"
assert {:ok, %Step{} = step} = Steps.update_step(step, update_attrs)
assert step.description == "some updated description"
assert step.position == 43
assert step.title == "some updated title"
end
test "update_step/3 with invalid data returns error changeset", %{
pipeline: pipeline,
user: user
} do
step = step_fixture(0, pipeline, user)
assert {:error, %Ecto.Changeset{}} = Steps.update_step(step, @invalid_attrs, user)
assert step == Steps.get_step!(step.id, user)
test "update_step/2 with invalid data returns error changeset" do
step = step_fixture()
assert {:error, %Ecto.Changeset{}} = Steps.update_step(step, @invalid_attrs)
assert step == Steps.get_step!(step.id)
end
test "delete_step/2 deletes the step", %{pipeline: pipeline, user: user} do
step = step_fixture(0, pipeline, user)
assert {:ok, %Step{}} = Steps.delete_step(step, user)
assert_raise Ecto.NoResultsError, fn -> Steps.get_step!(step.id, user) end
test "delete_step/1 deletes the step" do
step = step_fixture()
assert {:ok, %Step{}} = Steps.delete_step(step)
assert_raise Ecto.NoResultsError, fn -> Steps.get_step!(step.id) end
end
test "delete_step/2 moves past steps up", %{pipeline: pipeline, user: user} do
first_step = step_fixture(0, pipeline, user)
second_step = step_fixture(1, pipeline, user)
assert {:ok, %Step{}} = Steps.delete_step(first_step, user)
assert %{position: 0} = second_step |> Repo.reload!()
end
test "delete_step/2 deletes the step for an admin user", %{pipeline: pipeline, user: user} do
admin_user = admin_fixture()
step = step_fixture(0, pipeline, user)
assert {:ok, %Step{}} = Steps.delete_step(step, admin_user)
assert_raise Ecto.NoResultsError, fn -> Steps.get_step!(step.id, user) end
end
test "change_step/2 returns a step changeset", %{pipeline: pipeline, user: user} do
step = step_fixture(0, pipeline, user)
assert %Ecto.Changeset{} = Steps.change_step(step, user)
end
test "change_step/1 returns a step changeset", %{pipeline: pipeline, user: user} do
step = step_fixture(0, pipeline, user)
assert %Ecto.Changeset{} = Steps.change_step(step, user)
end
test "reorder_step/1 reorders steps properly", %{pipeline: pipeline, user: user} do
[
%{id: first_step_id} = first_step,
%{id: second_step_id} = second_step,
%{id: third_step_id} = third_step
] = Enum.map(0..2, fn index -> step_fixture(index, pipeline, user) end)
Steps.reorder_step(third_step, :up, user)
assert [
%{id: ^first_step_id, position: 0},
%{id: ^third_step_id, position: 1},
%{id: ^second_step_id, position: 2}
] = Steps.list_steps(pipeline, user)
Steps.reorder_step(first_step, :up, user)
assert [
%{id: ^first_step_id, position: 0},
%{id: ^third_step_id, position: 1},
%{id: ^second_step_id, position: 2}
] = Steps.list_steps(pipeline, user)
second_step
|> Repo.reload!()
|> Steps.reorder_step(:down, user)
assert [
%{id: ^first_step_id, position: 0},
%{id: ^third_step_id, position: 1},
%{id: ^second_step_id, position: 2}
] = Steps.list_steps(pipeline, user)
Steps.reorder_step(first_step, :down, user)
assert [
%{id: ^third_step_id, position: 0},
%{id: ^first_step_id, position: 1},
%{id: ^second_step_id, position: 2}
] = Steps.list_steps(pipeline, user)
test "change_step/1 returns a step changeset" do
step = step_fixture()
assert %Ecto.Changeset{} = Steps.change_step(step)
end
end
end

Some files were not shown because too many files have changed in this diff Show More