13 Commits

Author SHA1 Message Date
7813738f91 add json data export
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 19:15:08 -05:00
c1337ebc10 fix formatting in note/context/step contents 2022-12-20 18:36:56 -05:00
59283a0217 fix overflow on note/contexts/step contents
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 23:16:12 -05:00
634891ee73 Added translation using Weblate (German)
Some checks are pending
continuous-integration/drone/push Build is running
2022-12-20 03:35:57 +00:00
571f6fffdb improve tagging logic
Some checks are pending
continuous-integration/drone/push Build is running
2022-12-19 22:34:00 -05:00
926805ba9b fix user invite page 2022-12-19 21:14:14 -05:00
220122dec6 add newlines to content components 2022-12-19 21:09:57 -05:00
de399b4819 fix broken docker-compose 2022-12-19 21:09:50 -05:00
c3ceb877b2 search tags when on click
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-15 22:52:42 -05:00
0b4449c8a8 backlink to other notes in notes 2022-12-15 22:45:48 -05:00
6452f64fec check for slug uniqueness before submitting
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-28 00:20:57 -05:00
c0afc96b8d add to faq 2022-11-28 00:14:16 -05:00
2cfecc54a0 fix more typos 2022-11-27 22:55:23 -05:00
53 changed files with 795 additions and 312 deletions

View File

@ -1,3 +1,25 @@
# v0.1.6
- fix formatting in note/context/step contents
- add json export for data
# 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

10
de.tbx Normal file
View File

@ -0,0 +1,10 @@
<?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,8 +2,7 @@ version: '3'
services:
memex:
build:
context: .
image: shibaobun/memex
container_name: memex
restart: always
environment:

View File

@ -9,6 +9,16 @@ defmodule Memex.Accounts.User do
alias Ecto.{Changeset, UUID}
alias Memex.Invites.Invite
@derive {Jason.Encoder,
only: [
:id,
:email,
:confirmed_at,
:role,
:locale,
:inserted_at,
:updated_at
]}
@derive {Inspect, except: [:password]}
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id

View File

@ -4,7 +4,6 @@ defmodule Memex.Contexts do
"""
import Ecto.Query, warn: false
alias Ecto.Changeset
alias Memex.{Accounts.User, Contexts.Context, Repo}
@doc """
@ -229,18 +228,4 @@ defmodule Memex.Contexts do
def change_context(%Context{} = context, attrs \\ %{}, user) do
context |> Context.update_changeset(attrs, user)
end
@doc """
Gets a canonical string representation of the `:tags` field for a Note
"""
@spec get_tags_string(Context.t() | Context.changeset() | [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(%Context{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

@ -7,8 +7,17 @@ defmodule Memex.Contexts.Context do
import Ecto.Changeset
import MemexWeb.Gettext
alias Ecto.{Changeset, UUID}
alias Memex.Accounts.User
alias Memex.{Accounts.User, Repo}
@derive {Jason.Encoder,
only: [
:slug,
:content,
:tags,
:visibility,
:inserted_at,
:updated_at
]}
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "contexts" do
@ -27,7 +36,7 @@ defmodule Memex.Contexts.Context do
slug: slug(),
content: String.t(),
tags: [String.t()] | nil,
tags_string: String.t(),
tags_string: String.t() | nil,
visibility: :public | :private | :unlisted,
user: User.t() | Ecto.Association.NotLoaded.t(),
user_id: User.id(),
@ -49,6 +58,8 @@ defmodule Memex.Contexts.Context do
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)
end
@spec update_changeset(t(), attrs :: map(), User.t()) :: changeset()
@ -60,18 +71,42 @@ defmodule Memex.Contexts.Context do
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, %{"tags_string" => tags_string})
when tags_string |> is_binary() do
tags =
tags_string
|> String.split(",", trim: true)
|> Enum.map(fn str -> str |> String.trim() end)
|> Enum.sort()
changeset |> change(tags: tags)
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_string(changeset, _attrs), do: changeset
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

@ -4,7 +4,6 @@ defmodule Memex.Notes do
"""
import Ecto.Query, warn: false
alias Ecto.Changeset
alias Memex.{Accounts.User, Notes.Note, Repo}
@doc """
@ -229,18 +228,4 @@ defmodule Memex.Notes do
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() | Note.changeset() | [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

@ -6,8 +6,17 @@ defmodule Memex.Notes.Note do
import Ecto.Changeset
import MemexWeb.Gettext
alias Ecto.{Changeset, UUID}
alias Memex.Accounts.User
alias Memex.{Accounts.User, Repo}
@derive {Jason.Encoder,
only: [
:slug,
:content,
:tags,
:visibility,
:inserted_at,
:updated_at
]}
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "notes" do
@ -26,7 +35,7 @@ defmodule Memex.Notes.Note do
slug: slug(),
content: String.t(),
tags: [String.t()] | nil,
tags_string: String.t(),
tags_string: String.t() | nil,
visibility: :public | :private | :unlisted,
user: User.t() | Ecto.Association.NotLoaded.t(),
user_id: User.id(),
@ -48,6 +57,8 @@ defmodule Memex.Notes.Note do
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)
end
@spec update_changeset(t(), attrs :: map(), User.t()) :: changeset()
@ -59,18 +70,42 @@ defmodule Memex.Notes.Note do
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, %{"tags_string" => tags_string})
when tags_string |> is_binary() do
tags =
tags_string
|> String.split(",", trim: true)
|> Enum.map(fn str -> str |> String.trim() end)
|> Enum.sort()
changeset |> change(tags: tags)
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_string(changeset, _attrs), do: changeset
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

@ -4,7 +4,6 @@ defmodule Memex.Pipelines do
"""
import Ecto.Query, warn: false
alias Ecto.Changeset
alias Memex.{Accounts.User, Pipelines.Pipeline, Repo}
@doc """
@ -231,18 +230,4 @@ defmodule Memex.Pipelines do
def change_pipeline(%Pipeline{} = pipeline, attrs \\ %{}, user) do
pipeline |> Pipeline.update_changeset(attrs, user)
end
@doc """
Gets a canonical string representation of the `:tags` field for a Pipeline
"""
@spec get_tags_string(Pipeline.t() | Pipeline.changeset() | [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(%Pipeline{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

@ -6,8 +6,18 @@ defmodule Memex.Pipelines.Pipeline do
import Ecto.Changeset
import MemexWeb.Gettext
alias Ecto.{Changeset, UUID}
alias Memex.{Accounts.User, Pipelines.Steps.Step}
alias Memex.{Accounts.User, Pipelines.Steps.Step, Repo}
@derive {Jason.Encoder,
only: [
:slug,
:description,
:tags,
:visibility,
:inserted_at,
:steps,
:updated_at
]}
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "pipelines" do
@ -28,7 +38,7 @@ defmodule Memex.Pipelines.Pipeline do
slug: slug(),
description: String.t(),
tags: [String.t()] | nil,
tags_string: String.t(),
tags_string: String.t() | nil,
visibility: :public | :private | :unlisted,
user: User.t() | Ecto.Association.NotLoaded.t(),
user_id: User.id(),
@ -50,6 +60,8 @@ defmodule Memex.Pipelines.Pipeline do
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()
@ -61,18 +73,42 @@ defmodule Memex.Pipelines.Pipeline do
message: dgettext("errors", "invalid format: only numbers, letters and hyphen are accepted")
)
|> validate_required([:slug, :visibility])
|> unique_constraint(:slug)
|> unsafe_validate_unique(:slug, Repo)
end
defp cast_tags_string(changeset, %{"tags_string" => tags_string})
when tags_string |> is_binary() do
tags =
tags_string
|> String.split(",", trim: true)
|> Enum.map(fn str -> str |> String.trim() end)
|> Enum.sort()
changeset |> change(tags: tags)
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_string(changeset, _attrs), do: changeset
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

@ -7,6 +7,14 @@ defmodule Memex.Pipelines.Steps.Step do
alias Ecto.{Changeset, UUID}
alias Memex.{Accounts.User, Pipelines.Pipeline}
@derive {Jason.Encoder,
only: [
:title,
:content,
:position,
:inserted_at,
:updated_at
]}
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "steps" do

View File

@ -12,7 +12,7 @@ defmodule MemexWeb.Components.ContextContent do
~H"""
<div
id={"show-context-content-#{@context.id}"}
class="input input-primary h-128 min-h-128 inline-block"
class="input input-primary h-128 min-h-128 inline-block whitespace-pre-wrap overflow-x-hidden overflow-y-auto"
phx-hook="MaintainAttrs"
phx-update="ignore"
readonly

View File

@ -105,7 +105,17 @@ defmodule MemexWeb.Components.ContextsTableComponent do
end
defp get_value_for_key(:tags, %{tags: tags}, _additional_data) do
tags |> Enum.join(", ")
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

View File

@ -0,0 +1,44 @@
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 whitespace-pre-wrap 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
)
|> HTML.raw()
end
end

View File

@ -105,7 +105,17 @@ defmodule MemexWeb.Components.NotesTableComponent do
end
defp get_value_for_key(:tags, %{tags: tags}, _additional_data) do
tags |> Enum.join(", ")
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>
"""
end
defp get_value_for_key(:actions, note, %{actions: actions}) do

View File

@ -118,7 +118,17 @@ defmodule MemexWeb.Components.PipelinesTableComponent do
end
defp get_value_for_key(:tags, %{tags: tags}, _additional_data) do
tags |> Enum.join(", ")
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

View File

@ -12,7 +12,7 @@ defmodule MemexWeb.Components.StepContent do
~H"""
<div
id={"show-step-content-#{@step.id}"}
class="input input-primary h-32 min-h-32 inline-block"
class="input input-primary h-32 min-h-32 inline-block whitespace-pre-wrap overflow-x-hidden overflow-y-auto"
phx-hook="MaintainAttrs"
phx-update="ignore"
readonly

View File

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

View File

@ -0,0 +1,17 @@
defmodule MemexWeb.ExportController do
use MemexWeb, :controller
alias Memex.{Contexts, Notes, Pipelines, Pipelines.Steps}
def export(%{assigns: %{current_user: current_user}} = conn, %{"mode" => "json"}) do
pipelines =
Pipelines.list_pipelines(current_user)
|> Enum.map(fn pipeline -> Steps.preload_steps(pipeline, current_user) end)
json(conn, %{
user: current_user,
notes: Notes.list_notes(current_user),
contexts: Contexts.list_contexts(current_user),
pipelines: pipelines
})
end
end

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

@ -27,9 +27,7 @@
<%= text_input(f, :tags_string,
id: "tags-input",
class: "input input-primary",
placeholder: gettext("tag1,tag2"),
phx_update: "ignore",
value: Contexts.get_tags_string(@changeset)
placeholder: gettext("tag1,tag2")
) %>
<%= error_tag(f, :tags_string) %>

View File

@ -3,7 +3,13 @@
<%= @context.slug %>
</h1>
<p><%= if @context.tags, do: @context.tags |> Enum.join(", ") %></p>
<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} />

View File

@ -120,5 +120,26 @@
) %>
</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

@ -27,9 +27,7 @@
<%= text_input(f, :tags_string,
id: "tags-input",
class: "input input-primary",
placeholder: gettext("tag1,tag2"),
phx_update: "ignore",
value: Notes.get_tags_string(@changeset)
placeholder: gettext("tag1,tag2")
) %>
<%= error_tag(f, :tags_string) %>

View File

@ -1,6 +1,6 @@
defmodule MemexWeb.NoteLive.Show do
use MemexWeb, :live_view
import MemexWeb.Components.NoteContent
alias Memex.{Accounts.User, Notes, Notes.Note}
@impl true

View File

@ -3,16 +3,15 @@
<%= @note.slug %>
</h1>
<p><%= if @note.tags, do: @note.tags |> Enum.join(", ") %></p>
<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>
<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>
<.note_content note={@note} />
<p class="self-end">
<%= gettext("Visibility: %{visibility}", visibility: @note.visibility) %>

View File

@ -27,9 +27,7 @@
<%= text_input(f, :tags_string,
id: "tags-input",
class: "input input-primary",
placeholder: gettext("tag1,tag2"),
phx_update: "ignore",
value: Pipelines.get_tags_string(@changeset)
placeholder: gettext("tag1,tag2")
) %>
<%= error_tag(f, :tags_string) %>

View File

@ -3,7 +3,13 @@
<%= @pipeline.slug %>
</h1>
<p><%= if @pipeline.tags, do: @pipeline.tags |> Enum.join(", ") %></p>
<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

View File

@ -76,6 +76,7 @@ defmodule MemexWeb.Router do
put "/users/settings", UserSettingsController, :update
delete "/users/settings/:id", UserSettingsController, :delete
get "/users/settings/confirm_email/:token", UserSettingsController, :confirm_email
get "/export/:mode", ExportController, :export
end
scope "/", MemexWeb do

View File

@ -5,7 +5,7 @@
<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">

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

@ -136,12 +136,22 @@
<hr class="hr" />
<.link
href={Routes.user_settings_path(@conn, :delete, @current_user)}
method={:delete}
class="btn btn-alert"
data-confirm={dgettext("prompts", "are you sure you want to delete your account?")}
>
<%= dgettext("actions", "delete user") %>
</.link>
<div class="flex justify-center items-center">
<.link
href={Routes.export_path(@conn, :export, :json)}
class="mx-4 my-2 btn btn-primary"
target="_blank"
>
<%= dgettext("actions", "export data as json") %>
</.link>
<.link
href={Routes.user_settings_path(@conn, :delete, @current_user)}
method={:delete}
class="mx-4 my-2 btn btn-alert"
data-confirm={dgettext("prompts", "are you sure you want to delete your account?")}
>
<%= dgettext("actions", "delete user") %>
</.link>
</div>
</div>

View File

@ -4,7 +4,7 @@ defmodule Memex.MixProject do
def project do
[
app: :memex,
version: "0.1.1",
version: "0.1.6",
elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(),

View File

@ -15,13 +15,6 @@ msgstr ""
msgid "Copy to clipboard"
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_confirmation/new.html.heex:3
#: lib/memex_web/templates/user_confirmation/new.html.heex:15
#, elixir-autogen, elixir-format
@ -39,11 +32,6 @@ msgstr ""
msgid "Save"
msgstr ""
#: lib/memex_web/templates/user_reset_password/new.html.heex:15
#, elixir-autogen, elixir-format
msgid "Send instructions to reset password"
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
@ -68,28 +56,28 @@ msgid "create invite"
msgstr ""
#: lib/memex_web/live/context_live/index.html.heex:49
#: lib/memex_web/live/context_live/show.html.heex:34
#: 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:38
#: 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:43
#: lib/memex_web/live/pipeline_live/show.html.heex:113
#: 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
#: lib/memex_web/templates/user_settings/edit.html.heex:154
#, 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:23
#: 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:27
#: 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:32
#: lib/memex_web/live/pipeline_live/show.html.heex:102
#: 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 ""
@ -136,22 +124,39 @@ msgstr ""
msgid "register"
msgstr ""
#: lib/memex_web/live/context_live/form_component.html.heex:42
#: lib/memex_web/live/note_live/form_component.html.heex:42
#: lib/memex_web/live/pipeline_live/form_component.html.heex:42
#: 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:16
#: lib/memex_web/live/note_live/show.html.heex:23
#: lib/memex_web/live/pipeline_live/show.html.heex:25
#: 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:129
#: 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
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"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:145
#, elixir-autogen, elixir-format
msgid "export data as json"
msgstr ""

View File

@ -16,13 +16,6 @@ msgstr ""
msgid "Copy to clipboard"
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_confirmation/new.html.heex:3
#: lib/memex_web/templates/user_confirmation/new.html.heex:15
#, elixir-autogen, elixir-format
@ -40,11 +33,6 @@ msgstr ""
msgid "Save"
msgstr ""
#: lib/memex_web/templates/user_reset_password/new.html.heex:15
#, elixir-autogen, elixir-format
msgid "Send instructions to reset password"
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
@ -69,28 +57,28 @@ msgid "create invite"
msgstr ""
#: lib/memex_web/live/context_live/index.html.heex:49
#: lib/memex_web/live/context_live/show.html.heex:34
#: 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:38
#: 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:43
#: lib/memex_web/live/pipeline_live/show.html.heex:113
#: 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
#: lib/memex_web/templates/user_settings/edit.html.heex:154
#, 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:23
#: 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:27
#: 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:32
#: lib/memex_web/live/pipeline_live/show.html.heex:102
#: 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 ""
@ -137,22 +125,39 @@ msgstr ""
msgid "register"
msgstr ""
#: lib/memex_web/live/context_live/form_component.html.heex:42
#: lib/memex_web/live/note_live/form_component.html.heex:42
#: lib/memex_web/live/pipeline_live/form_component.html.heex:42
#: 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:16
#: lib/memex_web/live/note_live/show.html.heex:23
#: lib/memex_web/live/pipeline_live/show.html.heex:25
#: 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:129
#: 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 ""
#: lib/memex_web/templates/user_settings/edit.html.heex:145
#, elixir-autogen, elixir-format
msgid "export data as json"
msgstr ""

View File

@ -31,16 +31,6 @@ msgstr ""
msgid "Confirm your account"
msgstr ""
#: lib/memex_web/templates/user_registration/new.html.heex:36
#, elixir-autogen, elixir-format
msgid "English"
msgstr ""
#: lib/memex_web/controllers/user_reset_password_controller.ex:9
#, elixir-autogen, elixir-format
msgid "Forgot your password?"
msgstr ""
#: lib/memex_web/templates/user_session/new.html.heex:27
#, elixir-autogen, elixir-format
msgid "Keep me logged in for 60 days"
@ -76,11 +66,6 @@ msgstr ""
msgid "Settings"
msgstr ""
#: lib/memex_web/components/user_card.ex:33
#, elixir-autogen, elixir-format
msgid "User registered on"
msgstr ""
#: lib/memex_web/components/invite_card.ex:19
#, elixir-autogen, elixir-format
msgid "Uses Left:"
@ -91,9 +76,9 @@ msgstr ""
msgid "Uses left"
msgstr ""
#: lib/memex_web/live/context_live/show.html.heex:11
#: lib/memex_web/live/note_live/show.html.heex:18
#: lib/memex_web/live/pipeline_live/show.html.heex:20
#: 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 ""
@ -182,6 +167,7 @@ msgstr ""
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"
@ -315,17 +301,17 @@ msgstr ""
msgid "report bugs or request features"
msgstr ""
#: lib/memex_web/live/context_live/form_component.html.heex:43
#: lib/memex_web/live/note_live/form_component.html.heex:43
#: lib/memex_web/live/pipeline_live/form_component.html.heex:43
#: 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:39
#: lib/memex_web/live/note_live/form_component.html.heex:39
#: lib/memex_web/live/pipeline_live/form_component.html.heex:39
#: 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 ""
@ -359,11 +345,6 @@ msgstr ""
msgid "unlimited"
msgstr ""
#: lib/memex_web/components/user_card.ex:25
#, elixir-autogen, elixir-format
msgid "user was confirmed at %{relative_datetime}"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:120
#, elixir-autogen, elixir-format
msgid "users"
@ -510,7 +491,7 @@ msgstr ""
msgid "what is this?"
msgstr ""
#: lib/memex_web/live/pipeline_live/show.html.heex:62
#: lib/memex_web/live/pipeline_live/show.html.heex:68
#, elixir-autogen, elixir-format
msgid "%{position}. %{title}"
msgstr ""
@ -535,12 +516,12 @@ msgstr ""
msgid "add step to %{slug}"
msgstr ""
#: lib/memex_web/live/pipeline_live/show.html.heex:56
#: 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:51
#: lib/memex_web/live/pipeline_live/show.html.heex:57
#, elixir-autogen, elixir-format
msgid "steps:"
msgstr ""
@ -649,3 +630,38 @@ msgstr ""
#, 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

@ -76,22 +76,22 @@ msgstr ""
msgid "You must confirm your account and log in to access this page."
msgstr ""
#: lib/memex/accounts/user.ex:129
#: lib/memex/accounts/user.ex:139
#, elixir-autogen, elixir-format
msgid "did not change"
msgstr ""
#: lib/memex/accounts/user.ex:150
#: lib/memex/accounts/user.ex:160
#, elixir-autogen, elixir-format
msgid "does not match password"
msgstr ""
#: lib/memex/accounts/user.ex:187
#: lib/memex/accounts/user.ex:197
#, elixir-autogen, elixir-format
msgid "is not valid"
msgstr ""
#: lib/memex/accounts/user.ex:85
#: lib/memex/accounts/user.ex:95
#, elixir-autogen, elixir-format
msgid "must have the @ sign and no spaces"
msgstr ""
@ -102,12 +102,12 @@ msgstr ""
msgid "oops, something went wrong! Please check the errors below"
msgstr ""
#: lib/memex/contexts/context.ex:49
#: lib/memex/contexts/context.ex:60
#: lib/memex/notes/note.ex:48
#: lib/memex/notes/note.ex:59
#: lib/memex/pipelines/pipeline.ex:50
#: lib/memex/pipelines/pipeline.ex:61
#: lib/memex/contexts/context.ex:58
#: lib/memex/contexts/context.ex:71
#: lib/memex/notes/note.ex:57
#: lib/memex/notes/note.ex:70
#: lib/memex/pipelines/pipeline.ex:60
#: lib/memex/pipelines/pipeline.ex:73
#, elixir-autogen, elixir-format
msgid "invalid format: only numbers, letters and hyphen are accepted"
msgstr ""
@ -131,3 +131,10 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "unauthorized"
msgstr ""
#: lib/memex/contexts/context.ex:84
#: lib/memex/notes/note.ex:83
#: lib/memex/pipelines/pipeline.ex:86
#, elixir-autogen, elixir-format, fuzzy
msgid "invalid format: only numbers, letters and hyphen are accepted. tags must be comma-delimited"
msgstr ""

View File

@ -81,11 +81,6 @@ msgstr ""
msgid "Language updated successfully."
msgstr ""
#: lib/memex_web/controllers/user_session_controller.ex:23
#, elixir-autogen, elixir-format
msgid "Logged out successfully."
msgstr ""
#: lib/memex_web/controllers/user_reset_password_controller.ex:46
#, elixir-autogen, elixir-format
msgid "Password reset successfully."
@ -127,7 +122,7 @@ msgstr ""
msgid "are you sure you want to delete the invite for %{invite_name}?"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:143
#: lib/memex_web/templates/user_settings/edit.html.heex:152
#, elixir-autogen, elixir-format
msgid "are you sure you want to delete your account?"
msgstr ""
@ -143,12 +138,12 @@ 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:31
#: 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:35
#: 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:40
#: lib/memex_web/live/pipeline_live/show.html.heex:110
#: 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 ""
@ -157,3 +152,8 @@ msgstr ""
#, 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

@ -20,16 +20,6 @@ msgstr ""
msgid "Confirm your account"
msgstr ""
#: lib/memex_web/templates/user_registration/new.html.heex:36
#, elixir-autogen, elixir-format
msgid "English"
msgstr ""
#: lib/memex_web/controllers/user_reset_password_controller.ex:9
#, elixir-autogen, elixir-format
msgid "Forgot your password?"
msgstr ""
#: lib/memex_web/templates/user_session/new.html.heex:27
#, elixir-autogen, elixir-format
msgid "Keep me logged in for 60 days"
@ -65,11 +55,6 @@ msgstr ""
msgid "Settings"
msgstr ""
#: lib/memex_web/components/user_card.ex:33
#, elixir-autogen, elixir-format
msgid "User registered on"
msgstr ""
#: lib/memex_web/components/invite_card.ex:19
#, elixir-autogen, elixir-format
msgid "Uses Left:"
@ -80,9 +65,9 @@ msgstr ""
msgid "Uses left"
msgstr ""
#: lib/memex_web/live/context_live/show.html.heex:11
#: lib/memex_web/live/note_live/show.html.heex:18
#: lib/memex_web/live/pipeline_live/show.html.heex:20
#: 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 ""
@ -171,6 +156,7 @@ msgstr ""
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"
@ -304,17 +290,17 @@ msgstr ""
msgid "report bugs or request features"
msgstr ""
#: lib/memex_web/live/context_live/form_component.html.heex:43
#: lib/memex_web/live/note_live/form_component.html.heex:43
#: lib/memex_web/live/pipeline_live/form_component.html.heex:43
#: 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:39
#: lib/memex_web/live/note_live/form_component.html.heex:39
#: lib/memex_web/live/pipeline_live/form_component.html.heex:39
#: 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 ""
@ -348,11 +334,6 @@ msgstr ""
msgid "unlimited"
msgstr ""
#: lib/memex_web/components/user_card.ex:25
#, elixir-autogen, elixir-format
msgid "user was confirmed at %{relative_datetime}"
msgstr ""
#: lib/memex_web/live/invite_live/index.html.heex:120
#, elixir-autogen, elixir-format
msgid "users"
@ -499,7 +480,7 @@ msgstr ""
msgid "what is this?"
msgstr ""
#: lib/memex_web/live/pipeline_live/show.html.heex:62
#: lib/memex_web/live/pipeline_live/show.html.heex:68
#, elixir-autogen, elixir-format
msgid "%{position}. %{title}"
msgstr ""
@ -524,12 +505,12 @@ msgstr ""
msgid "add step to %{slug}"
msgstr ""
#: lib/memex_web/live/pipeline_live/show.html.heex:56
#: 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:51
#: lib/memex_web/live/pipeline_live/show.html.heex:57
#, elixir-autogen, elixir-format
msgid "steps:"
msgstr ""
@ -638,3 +619,38 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "memEx | %{title}"
msgstr ""
#: lib/memex_web/controllers/user_reset_password_controller.ex:9
#, elixir-autogen, elixir-format
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
msgid "user registered on"
msgstr ""

View File

@ -75,22 +75,22 @@ msgstr ""
msgid "You must confirm your account and log in to access this page."
msgstr ""
#: lib/memex/accounts/user.ex:129
#: lib/memex/accounts/user.ex:139
#, elixir-autogen, elixir-format
msgid "did not change"
msgstr ""
#: lib/memex/accounts/user.ex:150
#: lib/memex/accounts/user.ex:160
#, elixir-autogen, elixir-format
msgid "does not match password"
msgstr ""
#: lib/memex/accounts/user.ex:187
#: lib/memex/accounts/user.ex:197
#, elixir-autogen, elixir-format
msgid "is not valid"
msgstr ""
#: lib/memex/accounts/user.ex:85
#: lib/memex/accounts/user.ex:95
#, elixir-autogen, elixir-format
msgid "must have the @ sign and no spaces"
msgstr ""
@ -101,12 +101,12 @@ msgstr ""
msgid "oops, something went wrong! Please check the errors below"
msgstr ""
#: lib/memex/contexts/context.ex:49
#: lib/memex/contexts/context.ex:60
#: lib/memex/notes/note.ex:48
#: lib/memex/notes/note.ex:59
#: lib/memex/pipelines/pipeline.ex:50
#: lib/memex/pipelines/pipeline.ex:61
#: lib/memex/contexts/context.ex:58
#: lib/memex/contexts/context.ex:71
#: lib/memex/notes/note.ex:57
#: lib/memex/notes/note.ex:70
#: lib/memex/pipelines/pipeline.ex:60
#: lib/memex/pipelines/pipeline.ex:73
#, elixir-autogen, elixir-format
msgid "invalid format: only numbers, letters and hyphen are accepted"
msgstr ""
@ -130,3 +130,10 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "unauthorized"
msgstr ""
#: lib/memex/contexts/context.ex:84
#: lib/memex/notes/note.ex:83
#: lib/memex/pipelines/pipeline.ex:86
#, elixir-autogen, elixir-format
msgid "invalid format: only numbers, letters and hyphen are accepted. tags must be comma-delimited"
msgstr ""

View File

@ -80,11 +80,6 @@ msgstr ""
msgid "Language updated successfully."
msgstr ""
#: lib/memex_web/controllers/user_session_controller.ex:23
#, elixir-autogen, elixir-format
msgid "Logged out successfully."
msgstr ""
#: lib/memex_web/controllers/user_reset_password_controller.ex:46
#, elixir-autogen, elixir-format
msgid "Password reset successfully."
@ -126,7 +121,7 @@ msgstr ""
msgid "are you sure you want to delete the invite for %{invite_name}?"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:143
#: lib/memex_web/templates/user_settings/edit.html.heex:152
#, elixir-autogen, elixir-format
msgid "are you sure you want to delete your account?"
msgstr ""
@ -142,12 +137,12 @@ 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:31
#: 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:35
#: 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:40
#: lib/memex_web/live/pipeline_live/show.html.heex:110
#: 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 ""
@ -156,3 +151,8 @@ msgstr ""
#, 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."
msgstr ""

View File

@ -0,0 +1,101 @@
defmodule MemexWeb.ExportControllerTest do
@moduledoc """
Tests the export function
"""
use MemexWeb.ConnCase
import Memex.{ContextsFixtures, NotesFixtures, PipelinesFixtures, StepsFixtures}
@moduletag :export_controller_test
setup %{conn: conn} do
current_user = user_fixture() |> confirm_user()
[
current_user: current_user,
conn: conn |> log_in_user(current_user)
]
end
defp add_data(%{current_user: current_user}) do
note = note_fixture(current_user)
context = context_fixture(current_user)
pipeline = pipeline_fixture(current_user)
step = step_fixture(0, pipeline, current_user)
%{
note: note,
context: context,
pipeline: pipeline,
step: step
}
end
describe "Exports data" do
setup [:add_data]
test "in JSON", %{
conn: conn,
current_user: current_user,
note: note,
context: context,
pipeline: pipeline,
step: step
} do
conn = get(conn, Routes.export_path(conn, :export, :json))
ideal_note = %{
"slug" => note.slug,
"content" => note.content,
"tags" => note.tags,
"visibility" => note.visibility |> to_string(),
"inserted_at" => note.inserted_at |> NaiveDateTime.to_iso8601(),
"updated_at" => note.updated_at |> NaiveDateTime.to_iso8601()
}
ideal_context = %{
"slug" => context.slug,
"content" => context.content,
"tags" => context.tags,
"visibility" => context.visibility |> to_string(),
"inserted_at" => context.inserted_at |> NaiveDateTime.to_iso8601(),
"updated_at" => context.updated_at |> NaiveDateTime.to_iso8601()
}
ideal_pipeline = %{
"slug" => pipeline.slug,
"description" => pipeline.description,
"tags" => pipeline.tags,
"visibility" => pipeline.visibility |> to_string(),
"inserted_at" => pipeline.inserted_at |> NaiveDateTime.to_iso8601(),
"updated_at" => pipeline.updated_at |> NaiveDateTime.to_iso8601(),
"steps" => [
%{
"title" => step.title,
"content" => step.content,
"position" => step.position,
"inserted_at" => step.inserted_at |> NaiveDateTime.to_iso8601(),
"updated_at" => step.updated_at |> NaiveDateTime.to_iso8601()
}
]
}
ideal_user = %{
"confirmed_at" =>
current_user.confirmed_at |> Jason.encode!() |> String.replace(~r/\"/, ""),
"email" => current_user.email,
"id" => current_user.id,
"locale" => current_user.locale,
"role" => to_string(current_user.role),
"inserted_at" => current_user.inserted_at |> NaiveDateTime.to_iso8601(),
"updated_at" => current_user.updated_at |> NaiveDateTime.to_iso8601()
}
json_resp = conn |> json_response(200)
assert %{"notes" => [^ideal_note]} = json_resp
assert %{"contexts" => [^ideal_context]} = json_resp
assert %{"pipelines" => [^ideal_pipeline]} = json_resp
assert %{"user" => ^ideal_user} = json_resp
end
end
end

View File

@ -17,7 +17,7 @@ defmodule MemexWeb.UserResetPasswordControllerTest do
test "renders the reset password page", %{conn: conn} do
conn = get(conn, Routes.user_reset_password_path(conn, :new))
response = html_response(conn, 200)
assert response =~ dgettext("actions", "Forgot your password?")
assert response =~ dgettext("actions", "forgot your password?")
end
end

View File

@ -88,14 +88,14 @@ defmodule MemexWeb.UserSessionControllerTest do
conn = conn |> log_in_user(current_user) |> delete(Routes.user_session_path(conn, :delete))
assert redirected_to(conn) == "/"
refute get_session(conn, :user_token)
assert get_flash(conn, :info) =~ gettext("Logged out successfully")
assert get_flash(conn, :info) =~ gettext("logged out successfully")
end
test "succeeds even if the user is not logged in", %{conn: conn} do
conn = delete(conn, Routes.user_session_path(conn, :delete))
assert redirected_to(conn) == "/"
refute get_session(conn, :user_token)
assert get_flash(conn, :info) =~ gettext("Logged out successfully")
assert get_flash(conn, :info) =~ gettext("logged out successfully")
end
end
end

View File

@ -18,7 +18,7 @@ defmodule MemexWeb.ContextLiveTest do
}
@invalid_attrs %{
"content" => nil,
"tags_string" => "",
"tags_string" => " ",
"slug" => nil,
"visibility" => nil
}
@ -37,6 +37,14 @@ defmodule MemexWeb.ContextLiveTest do
assert html =~ context.slug
end
test "searches by tag", %{conn: conn} do
{:ok, index_live, html} = live(conn, Routes.context_index_path(conn, :index))
assert html =~ "example-tag"
assert index_live |> element("a", "example-tag") |> render_click()
assert_patch(index_live, Routes.context_index_path(conn, :search, "example-tag"))
end
test "saves new context", %{conn: conn} do
{:ok, index_live, _html} = live(conn, Routes.context_index_path(conn, :index))
@ -106,9 +114,13 @@ defmodule MemexWeb.ContextLiveTest do
assert_patch(show_live, Routes.context_show_path(conn, :edit, context.slug))
assert show_live
|> form("#context-form", context: @invalid_attrs)
|> render_change() =~ "can&#39;t be blank"
html =
show_live
|> form("#context-form", context: @invalid_attrs)
|> render_change()
assert html =~ "can&#39;t be blank"
assert html =~ "tags must be comma-delimited"
{:ok, _, html} =
show_live
@ -146,6 +158,14 @@ defmodule MemexWeb.ContextLiveTest do
]
end
test "searches by tag", %{conn: conn, context: context} do
{:ok, show_live, html} = live(conn, Routes.context_show_path(conn, :show, context.slug))
assert html =~ "example-tag"
assert show_live |> element("a", "example-tag") |> render_click()
assert_redirect(show_live, Routes.context_index_path(conn, :search, "example-tag"))
end
test "displays context", %{conn: conn, context: context, note: %{slug: note_slug}} do
{:ok, show_live, html} = live(conn, Routes.context_show_path(conn, :show, context.slug))

View File

@ -3,6 +3,7 @@ defmodule MemexWeb.NoteLiveTest do
import Phoenix.LiveViewTest
import Memex.NotesFixtures
alias MemexWeb.Endpoint
@create_attrs %{
"content" => "some content",
@ -18,7 +19,7 @@ defmodule MemexWeb.NoteLiveTest do
}
@invalid_attrs %{
"content" => nil,
"tags_string" => "",
"tags_string" => " ",
"slug" => nil,
"visibility" => nil
}
@ -37,6 +38,14 @@ defmodule MemexWeb.NoteLiveTest do
assert html =~ note.slug
end
test "searches by tag", %{conn: conn} do
{:ok, index_live, html} = live(conn, Routes.note_index_path(conn, :index))
assert html =~ "example-tag"
assert index_live |> element("a", "example-tag") |> render_click()
assert_patch(index_live, Routes.note_index_path(conn, :search, "example-tag"))
end
test "saves new note", %{conn: conn} do
{:ok, index_live, _html} = live(conn, Routes.note_index_path(conn, :index))
@ -45,9 +54,13 @@ defmodule MemexWeb.NoteLiveTest do
assert_patch(index_live, Routes.note_index_path(conn, :new))
assert index_live
|> form("#note-form", note: @invalid_attrs)
|> render_change() =~ "can&#39;t be blank"
html =
index_live
|> form("#note-form", note: @invalid_attrs)
|> render_change()
assert html =~ "can&#39;t be blank"
assert html =~ "tags must be comma-delimited"
{:ok, _, html} =
index_live
@ -132,4 +145,39 @@ defmodule MemexWeb.NoteLiveTest do
refute has_element?(index_live, "#note-#{note.id}")
end
end
describe "show with note" do
setup [:register_and_log_in_user]
setup %{user: user} do
%{slug: note_slug} = note = note_fixture(user)
[
note: note,
backlinked_note:
note_fixture(%{content: "example with backlink to [[#{note_slug}]] note"}, user)
]
end
test "searches by tag", %{conn: conn, note: note} do
{:ok, show_live, html} = live(conn, Routes.note_show_path(conn, :show, note.slug))
assert html =~ "example-tag"
assert show_live |> element("a", "example-tag") |> render_click()
assert_redirect(show_live, Routes.note_index_path(conn, :search, "example-tag"))
end
test "displays context", %{
conn: conn,
backlinked_note: %{slug: backlinked_note_slug},
note: %{slug: note_slug}
} do
{:ok, show_live, html} =
live(conn, Routes.note_show_path(conn, :show, backlinked_note_slug))
assert html =~ "context"
assert html =~ Routes.note_show_path(Endpoint, :show, note_slug)
assert has_element?(show_live, "[data-qa=\"note-link-#{note_slug}\"]")
end
end
end

View File

@ -17,7 +17,7 @@ defmodule MemexWeb.PipelineLiveTest do
}
@invalid_attrs %{
"description" => nil,
"tags_string" => "",
"tags_string" => " ",
"slug" => nil,
"visibility" => nil
}
@ -48,6 +48,14 @@ defmodule MemexWeb.PipelineLiveTest do
assert html =~ pipeline.description
end
test "searches by tag", %{conn: conn} do
{:ok, index_live, html} = live(conn, Routes.pipeline_index_path(conn, :index))
assert html =~ "example-tag"
assert index_live |> element("a", "example-tag") |> render_click()
assert_patch(index_live, Routes.pipeline_index_path(conn, :search, "example-tag"))
end
test "saves new pipeline", %{conn: conn} do
{:ok, index_live, _html} = live(conn, Routes.pipeline_index_path(conn, :index))
@ -120,9 +128,13 @@ defmodule MemexWeb.PipelineLiveTest do
assert_patch(show_live, Routes.pipeline_show_path(conn, :edit, pipeline.slug))
assert show_live
|> form("#pipeline-form", pipeline: @invalid_attrs)
|> render_change() =~ "can&#39;t be blank"
html =
show_live
|> form("#pipeline-form", pipeline: @invalid_attrs)
|> render_change()
assert html =~ "can&#39;t be blank"
assert html =~ "tags must be comma-delimited"
{:ok, _, html} =
show_live
@ -175,6 +187,14 @@ defmodule MemexWeb.PipelineLiveTest do
]
end
test "searches by tag", %{conn: conn, pipeline: pipeline} do
{:ok, show_live, html} = live(conn, Routes.pipeline_show_path(conn, :show, pipeline.slug))
assert html =~ "example-tag"
assert show_live |> element("a", "example-tag") |> render_click()
assert_redirect(show_live, Routes.pipeline_index_path(conn, :search, "example-tag"))
end
test "updates a step", %{conn: conn, pipeline: pipeline, step: step} do
{:ok, show_live, _html} = live(conn, Routes.pipeline_show_path(conn, :show, pipeline.slug))

View File

@ -16,12 +16,12 @@ defmodule Memex.ContextsFixtures do
attrs
|> Enum.into(%{
content: "some content",
tags: [],
tags: ["example-tag"],
slug: random_slug(),
visibility: :private
})
|> Contexts.create_context(user)
context
%{context | tags_string: nil}
end
end

View File

@ -16,12 +16,12 @@ defmodule Memex.NotesFixtures do
attrs
|> Enum.into(%{
content: "some content",
tags: [],
tags: ["example-tag"],
slug: random_slug(),
visibility: :private
})
|> Notes.create_note(user)
note
%{note | tags_string: nil}
end
end

View File

@ -16,12 +16,12 @@ defmodule Memex.PipelinesFixtures do
attrs
|> Enum.into(%{
description: "some description",
tags: [],
tags: ["example-tag"],
slug: random_slug(),
visibility: :private
})
|> Pipelines.create_pipeline(user)
pipeline
%{pipeline | tags_string: nil}
end
end