36 Commits

Author SHA1 Message Date
178111ce80 add qr code for invite link
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-26 00:46:27 -05:00
50c4878453 improve formatting 2023-01-26 00:36:32 -05:00
51e6f49d17 use list for classes 2023-01-25 23:11:31 -05:00
9289b79afa fix padding at bottom of page in chrome 2023-01-25 22:13:53 -05:00
87f4d18a6d add background to root 2023-01-25 22:06:24 -05:00
30bee92e37 improve locale 2023-01-25 21:53:58 -05:00
f32d52db26 fix webpack 2023-01-25 21:20:53 -05:00
d77421613c use topbar through npm 2023-01-25 21:19:41 -05:00
0d61e5ee96 bump version
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-23 00:06:25 -05:00
89413df8c4 fix gettext 2023-01-23 00:05:59 -05:00
8ccd94818c make loading screen less intrusive 2023-01-23 00:05:59 -05:00
482f339da2 show topbar in more situations 2023-01-23 00:05:59 -05:00
af911f211c update dependencies 2023-01-23 00:05:59 -05:00
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
501eb5d560 use project version on homepage
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-27 21:33:06 -05:00
16c4ae16d3 standardize changelog
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-27 21:25:17 -05:00
516fe4a9f7 include weblate changes 2022-11-27 21:24:47 -05:00
4a15f27923 fix some typos 2022-11-27 21:23:27 -05:00
dcfd1b87df improve table information 2022-11-27 21:18:35 -05:00
c0df3440f5 improve search 2022-11-27 21:10:54 -05:00
6f03bee761 fix gettext for emails 2022-11-27 19:58:56 -05:00
fe91d8de6d add german
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-27 13:02:32 -05:00
30535fe5e1 add german
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-27 00:10:40 -05:00
b7464cff38 update drone
Some checks failed
continuous-integration/drone/push Build is failing
2022-11-26 23:26:53 -05:00
83 changed files with 3327 additions and 1253 deletions

View File

@ -25,12 +25,13 @@ $fa-font-path: "@fortawesome/fontawesome-free/webfonts";
100% { scale: 1.0; opacity: 1; }
}
.phx-connected > #disconnect, #loading {
// disconnect toast
.phx-connected > #disconnect {
opacity: 0 !important;
pointer-events: none;
}
.phx-loading:not(.phx-error) > #loading, .phx-error > #disconnect {
.phx-error > #disconnect {
opacity: 0.95 !important;
}

View File

@ -25,7 +25,7 @@ import 'phoenix_html'
// Establish Phoenix Socket and LiveView configuration.
import { Socket } from 'phoenix'
import { LiveSocket } from 'phoenix_live_view'
import topbar from '../vendor/topbar'
import topbar from 'topbar'
import MaintainAttrs from './maintain_attrs'
import Alpine from 'alpinejs'
@ -50,6 +50,8 @@ Alpine.start()
topbar.config({ barThickness: 1, barColors: { 0: '#fff' }, shadowColor: 'rgba(0, 0, 0, .3)' })
window.addEventListener('phx:page-loading-start', info => topbar.show())
window.addEventListener('phx:page-loading-stop', info => topbar.hide())
window.addEventListener('submit', info => topbar.show())
window.addEventListener('beforeunload', info => topbar.show())
// connect if there are any LiveViews on the page
liveSocket.connect()

1482
assets/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,10 @@
"repository": {},
"description": " ",
"license": "MIT",
"engines": {
"node": "18.12.1",
"npm": "8.19.2"
},
"scripts": {
"deploy": "NODE_ENV=production webpack --mode production",
"watch": "webpack --mode development --watch --watch-options-stdin",
@ -26,16 +30,14 @@
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^3.4.1",
"file-loader": "^6.2.0",
"hard-source-webpack-plugin": "^0.13.1",
"mini-css-extract-plugin": "^2.6.0",
"node-sass": "^7.0.1",
"postcss": "^8.4.13",
"postcss-import": "^14.1.0",
"postcss-loader": "^6.2.1",
"postcss-preset-env": "^7.5.0",
"sass": "^1.56.0",
"sass-loader": "^12.6.0",
"standard": "^17.0.0",
"style-loader": "^3.3.1",
"tailwindcss": "^3.0.24",
"terser-webpack-plugin": "^5.3.1",
"webpack": "^5.72.0",

View File

@ -1,157 +0,0 @@
/**
* @license MIT
* topbar 1.0.0, 2021-01-06
* https://buunguyen.github.io/topbar
* Copyright (c) 2021 Buu Nguyen
*/
(function (window, document) {
"use strict";
// https://gist.github.com/paulirish/1579671
(function () {
var lastTime = 0;
var vendors = ["ms", "moz", "webkit", "o"];
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame =
window[vendors[x] + "RequestAnimationFrame"];
window.cancelAnimationFrame =
window[vendors[x] + "CancelAnimationFrame"] ||
window[vendors[x] + "CancelRequestAnimationFrame"];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function (callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function () {
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function (id) {
clearTimeout(id);
};
})();
var canvas,
progressTimerId,
fadeTimerId,
currentProgress,
showing,
addEvent = function (elem, type, handler) {
if (elem.addEventListener) elem.addEventListener(type, handler, false);
else if (elem.attachEvent) elem.attachEvent("on" + type, handler);
else elem["on" + type] = handler;
},
options = {
autoRun: true,
barThickness: 3,
barColors: {
0: "rgba(26, 188, 156, .9)",
".25": "rgba(52, 152, 219, .9)",
".50": "rgba(241, 196, 15, .9)",
".75": "rgba(230, 126, 34, .9)",
"1.0": "rgba(211, 84, 0, .9)",
},
shadowBlur: 10,
shadowColor: "rgba(0, 0, 0, .6)",
className: null,
},
repaint = function () {
canvas.width = window.innerWidth;
canvas.height = options.barThickness * 5; // need space for shadow
var ctx = canvas.getContext("2d");
ctx.shadowBlur = options.shadowBlur;
ctx.shadowColor = options.shadowColor;
var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
for (var stop in options.barColors)
lineGradient.addColorStop(stop, options.barColors[stop]);
ctx.lineWidth = options.barThickness;
ctx.beginPath();
ctx.moveTo(0, options.barThickness / 2);
ctx.lineTo(
Math.ceil(currentProgress * canvas.width),
options.barThickness / 2
);
ctx.strokeStyle = lineGradient;
ctx.stroke();
},
createCanvas = function () {
canvas = document.createElement("canvas");
var style = canvas.style;
style.position = "fixed";
style.top = style.left = style.right = style.margin = style.padding = 0;
style.zIndex = 100001;
style.display = "none";
if (options.className) canvas.classList.add(options.className);
document.body.appendChild(canvas);
addEvent(window, "resize", repaint);
},
topbar = {
config: function (opts) {
for (var key in opts)
if (options.hasOwnProperty(key)) options[key] = opts[key];
},
show: function () {
if (showing) return;
showing = true;
if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId);
if (!canvas) createCanvas();
canvas.style.opacity = 1;
canvas.style.display = "block";
topbar.progress(0);
if (options.autoRun) {
(function loop() {
progressTimerId = window.requestAnimationFrame(loop);
topbar.progress(
"+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2)
);
})();
}
},
progress: function (to) {
if (typeof to === "undefined") return currentProgress;
if (typeof to === "string") {
to =
(to.indexOf("+") >= 0 || to.indexOf("-") >= 0
? currentProgress
: 0) + parseFloat(to);
}
currentProgress = to > 1 ? 1 : to;
repaint();
return currentProgress;
},
hide: function () {
if (!showing) return;
showing = false;
if (progressTimerId != null) {
window.cancelAnimationFrame(progressTimerId);
progressTimerId = null;
}
(function loop() {
if (topbar.progress("+.1") >= 1) {
canvas.style.opacity -= 0.05;
if (canvas.style.opacity <= 0.05) {
canvas.style.display = "none";
fadeTimerId = null;
return;
}
}
fadeTimerId = window.requestAnimationFrame(loop);
})();
},
};
if (typeof module === "object" && typeof module.exports === "object") {
module.exports = topbar;
} else if (typeof define === "function" && define.amd) {
define(function () {
return topbar;
});
} else {
this.topbar = topbar;
}
}.call(this, window, document));

36
changelog.md Normal file
View File

@ -0,0 +1,36 @@
# v0.1.7
- Update dependencies
- Show topbar on form submit/page refresh
- Make loading/reconnection less intrusive
- Add QR code for invite link
# 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
- fix some typos
- use project version on homepage
# v0.1.0
- initial release >:3c

View File

@ -13,17 +13,18 @@ if System.get_env("PHX_SERVER") && System.get_env("RELEASE_NAME") do
end
# Set default locale
config :gettext, :default_locale, System.get_env("LOCALE") || "en_US"
config :gettext, :default_locale, System.get_env("LOCALE", "en_US")
maybe_ipv6 = if System.get_env("ECTO_IPV6") == "true", do: [:inet6], else: []
database_url =
if config_env() == :test do
System.get_env("TEST_DATABASE_URL") ||
System.get_env(
"TEST_DATABASE_URL",
"ecto://postgres:postgres@localhost/memex_test#{System.get_env("MIX_TEST_PARTITION")}"
)
else
System.get_env("DATABASE_URL") ||
"ecto://postgres:postgres@memex-db/memex"
System.get_env("DATABASE_URL", "ecto://postgres:postgres@memex-db/memex")
end
host =
@ -38,7 +39,7 @@ interface =
config :memex, Memex.Repo,
# ssl: true,
url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
pool_size: String.to_integer(System.get_env("POOL_SIZE", "10")),
socket_options: maybe_ipv6
config :memex, MemexWeb.Endpoint,
@ -47,10 +48,10 @@ config :memex, MemexWeb.Endpoint,
# See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html
# for details about using IPv6 vs IPv4 and loopback vs public addresses.
ip: interface,
port: String.to_integer(System.get_env("PORT") || "4000")
port: String.to_integer(System.get_env("PORT", "4000"))
],
server: true,
registration: System.get_env("REGISTRATION") || "invite"
registration: System.get_env("REGISTRATION", "invite")
if config_env() == :prod do
# The secret key base is used to sign/encrypt cookies and other secrets.
@ -74,12 +75,12 @@ if config_env() == :prod do
config :memex, Memex.Mailer,
adapter: Swoosh.Adapters.SMTP,
relay: System.get_env("SMTP_HOST") || raise("No SMTP_HOST set!"),
port: System.get_env("SMTP_PORT") || 587,
port: System.get_env("SMTP_PORT", 587),
username: System.get_env("SMTP_USERNAME") || raise("No SMTP_USERNAME set!"),
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_from: System.get_env("EMAIL_FROM", "no-reply@#{System.get_env("HOST")}"),
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 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:
@ -25,8 +24,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:

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 """
@ -35,13 +34,13 @@ defmodule Memex.Contexts do
where: c.user_id == ^user_id,
where:
fragment(
"search @@ to_tsquery(websearch_to_tsquery(?)::text || ':*')",
"search @@ websearch_to_tsquery('english', ?)",
^trimmed_search
),
order_by: {
:desc,
fragment(
"ts_rank_cd(search, to_tsquery(websearch_to_tsquery(?)::text || ':*'), 4)",
"ts_rank_cd(search, websearch_to_tsquery('english', ?), 4)",
^trimmed_search
)
}
@ -76,13 +75,13 @@ defmodule Memex.Contexts do
where: c.visibility == :public,
where:
fragment(
"search @@ to_tsquery(websearch_to_tsquery(?)::text || ':*')",
"search @@ websearch_to_tsquery('english', ?)",
^trimmed_search
),
order_by: {
:desc,
fragment(
"ts_rank_cd(search, to_tsquery(websearch_to_tsquery(?)::text || ':*'), 4)",
"ts_rank_cd(search, websearch_to_tsquery('english', ?), 4)",
^trimmed_search
)
}
@ -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 """
@ -35,13 +34,13 @@ defmodule Memex.Notes do
where: n.user_id == ^user_id,
where:
fragment(
"search @@ to_tsquery(websearch_to_tsquery(?)::text || ':*')",
"search @@ websearch_to_tsquery('english', ?)",
^trimmed_search
),
order_by: {
:desc,
fragment(
"ts_rank_cd(search, to_tsquery(websearch_to_tsquery(?)::text || ':*'), 4)",
"ts_rank_cd(search, websearch_to_tsquery('english', ?), 4)",
^trimmed_search
)
}
@ -75,13 +74,13 @@ defmodule Memex.Notes do
where: n.visibility == :public,
where:
fragment(
"search @@ to_tsquery(websearch_to_tsquery(?)::text || ':*')",
"search @@ websearch_to_tsquery('english', ?)",
^trimmed_search
),
order_by: {
:desc,
fragment(
"ts_rank_cd(search, to_tsquery(websearch_to_tsquery(?)::text || ':*'), 4)",
"ts_rank_cd(search, websearch_to_tsquery('english', ?), 4)",
^trimmed_search
)
}
@ -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 """
@ -35,13 +34,13 @@ defmodule Memex.Pipelines do
where: p.user_id == ^user_id,
where:
fragment(
"search @@ to_tsquery(websearch_to_tsquery(?)::text || ':*')",
"search @@ websearch_to_tsquery('english', ?)",
^trimmed_search
),
order_by: {
:desc,
fragment(
"ts_rank_cd(search, to_tsquery(websearch_to_tsquery(?)::text || ':*'), 4)",
"ts_rank_cd(search, websearch_to_tsquery('english', ?), 4)",
^trimmed_search
)
}
@ -75,13 +74,13 @@ defmodule Memex.Pipelines do
where: p.visibility == :public,
where:
fragment(
"search @@ to_tsquery(websearch_to_tsquery(?)::text || ':*')",
"search @@ websearch_to_tsquery('english', ?)",
^trimmed_search
),
order_by: {
:desc,
fragment(
"ts_rank_cd(search, to_tsquery(websearch_to_tsquery(?)::text || ':*'), 4)",
"ts_rank_cd(search, websearch_to_tsquery('english', ?), 4)",
^trimmed_search
)
}
@ -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

@ -46,7 +46,7 @@ defmodule MemexWeb do
def live_view do
quote do
use Phoenix.LiveView,
layout: {MemexWeb.LayoutView, :live}
layout: {MemexWeb.LayoutView, "live.html"}
on_mount MemexWeb.InitAssigns
unquote(view_helpers())

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

@ -4,7 +4,7 @@ defmodule MemexWeb.Components.ContextsTableComponent do
"""
use MemexWeb, :live_component
alias Ecto.UUID
alias Memex.{Accounts.User, Contexts, Contexts.Context}
alias Memex.{Accounts.User, Contexts.Context}
alias Phoenix.LiveView.{Rendered, Socket}
@impl true
@ -45,7 +45,6 @@ defmodule MemexWeb.Components.ContextsTableComponent do
columns = [
%{label: gettext("slug"), key: :slug},
%{label: gettext("content"), key: :content},
%{label: gettext("tags"), key: :tags},
%{label: gettext("visibility"), key: :visibility}
| columns
@ -105,20 +104,18 @@ defmodule MemexWeb.Components.ContextsTableComponent do
{slug, slug_block}
end
defp get_value_for_key(:content, %{content: content}, _additional_data) do
assigns = %{content: content}
defp get_value_for_key(:tags, %{tags: tags}, _additional_data) do
assigns = %{tags: tags}
content_block = ~H"""
<div class="truncate max-w-sm">
<%= @content %>
~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>
"""
{content, content_block}
end
defp get_value_for_key(:tags, %{tags: tags}, _additional_data) do
tags |> Contexts.get_tags_string()
end
defp get_value_for_key(:actions, context, %{actions: actions}) do

View File

@ -6,6 +6,8 @@ defmodule MemexWeb.Components.InviteCard do
use MemexWeb, :component
def invite_card(assigns) do
assigns = assigns |> assign_new(:code_actions, fn -> [] end)
~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
@ -16,8 +18,14 @@ defmodule MemexWeb.Components.InviteCard do
<%= if @invite.disabled_at |> is_nil() do %>
<h2 class="title text-md">
<%= gettext("Uses Left:") %>
<%= @invite.uses_left || gettext("unlimited") %>
<%= if @invite.uses_left do %>
<%= gettext(
"uses left: %{uses_left}",
uses_left: @invite.uses_left
) %>
<% else %>
<%= gettext("uses left: unlimited") %>
<% end %>
</h2>
<% else %>
<h2 class="title text-md">
@ -25,17 +33,18 @@ defmodule MemexWeb.Components.InviteCard do
</h2>
<% end %>
<.qr_code
content={Routes.user_registration_url(Endpoint, :new, invite: @invite.token)}
filename={@invite.name}
/>
<div class="flex flex-row flex-wrap justify-center items-center">
<code
id={"code-#{@invite.id}"}
class="mx-2 my-1 text-xs px-4 py-2 rounded-lg text-center break-all text-gray-100 bg-primary-800"
>
<%= Routes.user_registration_url(Endpoint, :new, invite: @invite.token) %>
</code>
<%= if @code_actions do %>
<%= render_slot(@code_actions) %>
<% end %>
phx-no-format
><%= Routes.user_registration_url(Endpoint, :new, invite: @invite.token) %></code>
<%= render_slot(@code_actions) %>
</div>
<%= if @inner_block 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

@ -4,7 +4,7 @@ defmodule MemexWeb.Components.NotesTableComponent do
"""
use MemexWeb, :live_component
alias Ecto.UUID
alias Memex.{Accounts.User, Notes, Notes.Note}
alias Memex.{Accounts.User, Notes.Note}
alias Phoenix.LiveView.{Rendered, Socket}
@impl true
@ -45,7 +45,6 @@ defmodule MemexWeb.Components.NotesTableComponent do
columns = [
%{label: gettext("slug"), key: :slug},
%{label: gettext("content"), key: :content},
%{label: gettext("tags"), key: :tags},
%{label: gettext("visibility"), key: :visibility}
| columns
@ -105,20 +104,18 @@ defmodule MemexWeb.Components.NotesTableComponent do
{slug, slug_block}
end
defp get_value_for_key(:content, %{content: content}, _additional_data) do
assigns = %{content: content}
defp get_value_for_key(:tags, %{tags: tags}, _additional_data) do
assigns = %{tags: tags}
content_block = ~H"""
<div class="truncate max-w-sm">
<%= @content %>
~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>
"""
{content, content_block}
end
defp get_value_for_key(:tags, %{tags: tags}, _additional_data) do
tags |> Notes.get_tags_string()
end
defp get_value_for_key(:actions, note, %{actions: actions}) do

View File

@ -4,7 +4,7 @@ defmodule MemexWeb.Components.PipelinesTableComponent do
"""
use MemexWeb, :live_component
alias Ecto.UUID
alias Memex.{Accounts.User, Pipelines, Pipelines.Pipeline}
alias Memex.{Accounts.User, Pipelines.Pipeline}
alias Phoenix.LiveView.{Rendered, Socket}
@impl true
@ -118,7 +118,17 @@ defmodule MemexWeb.Components.PipelinesTableComponent do
end
defp get_value_for_key(:tags, %{tags: tags}, _additional_data) do
tags |> Pipelines.get_tags_string()
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

@ -4,7 +4,7 @@
<tr>
<%= for %{key: key, label: label} = column <- @columns do %>
<%= if column |> Map.get(:sortable, true) do %>
<th class={"p-2 #{column[:class]}"}>
<th class={["p-2", column[:class]]}>
<span
class="cursor-pointer flex justify-center items-center space-x-2"
phx-click="sort_by"
@ -25,7 +25,7 @@
</span>
</th>
<% else %>
<th class={"p-2 #{column[:class]}"}>
<th class={["p-2", column[:class]]}>
<%= label %>
</th>
<% end %>
@ -36,7 +36,7 @@
<%= for {values, i} <- @rows |> Enum.with_index() do %>
<tr class={if i |> Integer.is_even(), do: @row_class, else: @alternate_row_class}>
<%= for %{key: key} = value <- @columns do %>
<td class={"p-2 #{value[:class]}"}>
<td class={["p-2", value[:class]]}>
<%= case values |> Map.get(key) do %>
<% {_custom_sort_value, value} -> %>
<%= value %>

View File

@ -19,19 +19,23 @@ defmodule MemexWeb.Components.UserCard do
<h3 class="px-4 py-2 rounded-lg title text-lg">
<p>
<%= if @user.confirmed_at |> is_nil() do %>
<%= gettext("email unconfirmed") %>
<% else %>
<%= if @user.confirmed_at do %>
<%= gettext(
"user was confirmed at %{relative_datetime}",
relative_datetime: @user.confirmed_at |> display_datetime()
"user confirmed on%{confirmed_datetime}",
confirmed_datetime: ""
) %>
<.datetime datetime={@user.confirmed_at} />
<% else %>
<%= gettext("email unconfirmed") %>
<% end %>
</p>
<p>
<%= gettext("User registered on") %>
<%= @user.inserted_at |> display_datetime() %>
<%= gettext(
"user registered on%{registered_datetime}",
registered_datetime: ""
) %>
<.datetime datetime={@user.inserted_at} />
</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

@ -62,7 +62,7 @@ defmodule MemexWeb.UserSettingsController do
case Accounts.update_user_locale(user, locale) do
{:ok, _user} ->
conn
|> put_flash(:info, dgettext("prompts", "Language updated successfully."))
|> put_flash(:info, dgettext("prompts", "language updated successfully."))
|> redirect(to: Routes.user_settings_path(conn, :edit))
{:error, changeset} ->

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

@ -3,6 +3,8 @@ 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}
@ -10,6 +12,6 @@ defmodule MemexWeb.HomeLive do
@impl true
def mount(_params, _session, socket) do
admins = Accounts.list_users_by_role(:admin)
{:ok, socket |> assign(page_title: gettext("home"), admins: admins)}
{:ok, socket |> assign(page_title: gettext("home"), admins: admins, version: @version)}
end
end

View File

@ -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"
href="https://gitea.bubbletea.dev/shibao/memEx/src/branch/stable/changelog.md"
class="flex flex-row justify-center items-center space-x-2 link"
target="_blank"
rel="noopener noreferrer"
>
<p>0.1.0</p>
<p><%= @version %></p>
<i class="fas fa-md fa-info-circle"></i>
</.link>
</li>
@ -140,7 +140,7 @@
<li class="flex flex-col justify-center space-x-2">
<.link
href="https://gitea.bubbletea.dev/shibao/memex"
href="https://gitea.bubbletea.dev/shibao/memEx"
class="flex flex-row justify-center items-center space-x-2 link"
target="_blank"
rel="noopener noreferrer"
@ -151,7 +151,7 @@
</li>
<li class="flex flex-col justify-center space-x-2">
<.link
href="https://weblate.bubbletea.dev/engage/memex"
href="https://weblate.bubbletea.dev/engage/memEx"
class="flex flex-row justify-center items-center space-x-2 link"
target="_blank"
rel="noopener noreferrer"
@ -162,7 +162,7 @@
</li>
<li class="flex flex-col justify-center space-x-2">
<.link
href="https://gitea.bubbletea.dev/shibao/memex/issues/new"
href="https://gitea.bubbletea.dev/shibao/memEx/issues/new"
class="flex flex-row justify-center items-center space-x-2 link"
target="_blank"
rel="noopener noreferrer"

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

@ -11,15 +11,17 @@ defmodule MemexWeb.Router do
plug :protect_from_forgery
plug :put_secure_browser_headers
plug :fetch_current_user
plug :put_user_locale, default: Application.compile_env(:gettext, :default_locale, "en_US")
plug :put_user_locale
end
defp put_user_locale(%{assigns: %{current_user: %{locale: locale}}} = conn, default: default) do
defp put_user_locale(%{assigns: %{current_user: %{locale: locale}}} = conn, _opts) do
default = Application.fetch_env!(:gettext, :default_locale)
Gettext.put_locale(locale || default)
conn |> put_session(:locale, locale || default)
end
defp put_user_locale(conn, default: default) do
defp put_user_locale(conn, _opts) do
default = Application.fetch_env!(:gettext, :default_locale)
Gettext.put_locale(default)
conn |> put_session(:locale, default)
end
@ -76,6 +78,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

@ -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,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

@ -1,4 +1,4 @@
<main class="mb-8 min-w-full">
<main class="pb-8 min-w-full">
<header>
<.topbar current_user={assigns[:current_user]}></.topbar>
@ -27,28 +27,16 @@
</div>
</main>
<div
id="loading"
class="fixed opacity-0 top-0 left-0 w-screen h-screen bg-primary-900 z-50
flex flex-col justify-center items-center space-y-4
transition-opacity ease-in-out duration-500"
>
<h1 class="title text-2xl title-primary-500 text-primary-400">
<%= gettext("Loading...") %>
</h1>
<i class="fas fa-3x fa-spin fa-gear text-primary-400"></i>
</div>
<div
id="disconnect"
class="fixed opacity-0 top-0 left-0 w-screen h-screen bg-primary-900 z-50
flex flex-col justify-center items-center space-y-4
transition-opacity ease-in-out duration-500"
class="z-50 fixed opacity-0 bottom-12 right-12 px-8 py-4 w-max h-max
border border-primary-400 shadow-lg rounded-lg bg-primary-800 text-primary-400
flex justify-center items-center space-x-4
transition-opacity ease-in-out duration-500 delay-[2000ms]"
>
<h1 class="title text-2xl title-primary-500 text-primary-400">
<i class="fas fa-fade text-md fa-satellite-dish"></i>
<h1 class="title text-md">
<%= gettext("Reconnecting...") %>
</h1>
<i class="fas fa-3x fa-fade fa-satellite-dish text-primary-400"></i>
</div>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en" class="m-0 p-0 w-full h-full">
<html lang="en" class="m-0 p-0 w-full h-full bg-primary-800">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
@ -18,7 +18,7 @@
</script>
</head>
<body class="m-0 p-0 w-full h-full bg-primary-800 text-primary-400 subpixel-antialiased">
<body class="m-0 p-0 w-full h-full text-primary-400 subpixel-antialiased">
<%= @inner_content %>
</body>
</html>

View File

@ -29,11 +29,11 @@
<%= password_input(f, :password, required: true, class: "input input-primary col-span-2") %>
<%= error_tag(f, :password, "col-span-3") %>
<%= label(f, :locale, gettext("Language"), class: "title text-lg text-primary-400") %>
<%= label(f, :locale, gettext("language"), class: "title text-lg text-primary-400") %>
<%= 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

@ -18,7 +18,7 @@ defmodule MemexWeb.ErrorHelpers do
~H"""
<%= for error <- Keyword.get_values(@form.errors, @field) do %>
<span class={"invalid-feedback #{@extra_class}"} phx-feedback-for={input_name(@form, @field)}>
<span class={["invalid-feedback", @extra_class]} phx-feedback-for={input_name(@form, @field)}>
<%= translate_error(error) %>
</span>
<% end %>

View File

@ -1,17 +1,12 @@
defmodule MemexWeb.LayoutView do
use MemexWeb, :view
import MemexWeb.Components.Topbar
import MemexWeb.{Components.Topbar, Gettext}
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(conn) do
if conn.assigns |> Map.has_key?(:title) do
"Memex | #{conn.assigns.title}"
else
"Memex"
end
end
def get_title(%{assigns: %{title: title}}), do: gettext("memEx | %{title}", title: title)
def get_title(_conn), do: gettext("memEx")
end

View File

@ -5,56 +5,94 @@ defmodule MemexWeb.ViewHelpers do
:view`
"""
import Phoenix.Component
use Phoenix.Component
@doc """
Returns a <time> element that renders the naivedatetime in the user's local
timezone with Alpine.js
Phoenix.Component for a <time> element that renders the naivedatetime in the
user's local timezone with Alpine.js
"""
@spec display_datetime(NaiveDateTime.t() | nil) :: Phoenix.LiveView.Rendered.t()
def display_datetime(nil), do: ""
def display_datetime(datetime) do
assigns = %{
datetime: datetime |> DateTime.from_naive!("Etc/UTC") |> DateTime.to_iso8601(:extended)
}
attr :datetime, :any, required: true, doc: "A `DateTime` struct or nil"
def datetime(assigns) do
~H"""
<time
datetime={@datetime}
x-data={"{
date:
Intl.DateTimeFormat([], {dateStyle: 'short', timeStyle: 'long'})
.format(new Date(\"#{@datetime}\"))
}"}
x-text="date"
>
<%= @datetime %>
</time>
<%= if @datetime do %>
<time
datetime={cast_datetime(@datetime)}
x-data={"{
datetime:
Intl.DateTimeFormat([], {dateStyle: 'short', timeStyle: 'long'})
.format(new Date(\"#{cast_datetime(@datetime)}\"))
}"}
x-text="datetime"
>
<%= cast_datetime(@datetime) %>
</time>
<% end %>
"""
end
@spec cast_datetime(NaiveDateTime.t() | nil) :: String.t()
defp cast_datetime(%NaiveDateTime{} = datetime) do
datetime |> DateTime.from_naive!("Etc/UTC") |> DateTime.to_iso8601(:extended)
end
defp cast_datetime(_datetime), do: ""
@doc """
Phoenix.Component for a <date> element that renders the Date in the user's
local timezone with Alpine.js
"""
attr :date, :any, required: true, doc: "A `Date` struct or nil"
def date(assigns) do
~H"""
<%= if @date do %>
<time
datetime={@date |> Date.to_iso8601(:extended)}
x-data={"{
date:
Intl.DateTimeFormat([], {timeZone: 'Etc/UTC', dateStyle: 'short'})
.format(new Date(\"#{@date |> Date.to_iso8601(:extended)}\"))
}"}
x-text="date"
>
<%= @date |> Date.to_iso8601(:extended) %>
</time>
<% end %>
"""
end
@doc """
Returns a <date> element that renders the Date in the user's local
timezone with Alpine.js
Displays content in a QR code as a base64 encoded PNG
"""
@spec display_date(Date.t() | nil) :: Phoenix.LiveView.Rendered.t()
def display_date(nil), do: ""
@spec qr_code_image(String.t()) :: String.t()
@spec qr_code_image(String.t(), width :: non_neg_integer()) :: String.t()
def qr_code_image(content, width \\ 384) do
img_data =
content
|> EQRCode.encode()
|> EQRCode.png(width: width, background_color: <<39, 39, 42>>, color: <<255, 255, 255>>)
|> Base.encode64()
def display_date(date) do
assigns = %{date: date |> Date.to_iso8601(:extended)}
"data:image/png;base64," <> img_data
end
@doc """
Creates a downloadable QR Code element
"""
attr :content, :string, required: true
attr :filename, :string, default: "qrcode", doc: "filename without .png extension"
attr :image_class, :string, default: "w-64 h-max"
attr :width, :integer, default: 384, doc: "width of png to generate"
def qr_code(assigns) do
~H"""
<time
datetime={@date}
x-data={"{
date:
Intl.DateTimeFormat([], {timeZone: 'Etc/UTC', dateStyle: 'short'}).format(new Date(\"#{@date}\"))
}"}
x-text="date"
>
<%= @date %>
</time>
<a href={qr_code_image(@content)} download={@filename <> ".png"}>
<img class={@image_class} alt={@filename} src={qr_code_image(@content)} />
</a>
"""
end
end

18
mix.exs
View File

@ -4,7 +4,7 @@ defmodule Memex.MixProject do
def project do
[
app: :memex,
version: "0.1.0",
version: "0.1.7",
elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(),
@ -13,7 +13,7 @@ defmodule Memex.MixProject do
deps: deps(),
dialyzer: [plt_add_apps: [:ex_unit]],
consolidate_protocols: Mix.env() not in [:dev, :test],
preferred_cli_env: [test: :test, "test.all": :test],
preferred_cli_env: ["test.all": :test],
# ExDoc
name: "memEx",
source_url: "https://gitea.bubbletea.dev/shibao/memEx",
@ -48,27 +48,29 @@ defmodule Memex.MixProject do
defp deps do
[
{:bcrypt_elixir, "~> 2.0"},
{:phoenix, "~> 1.6.6"},
{:phoenix, "~> 1.6.0"},
{:phoenix_ecto, "~> 4.4"},
{:ecto_sql, "~> 3.6"},
{:postgrex, ">= 0.0.0"},
{:phoenix_html, "~> 3.0"},
{:phoenix_live_reload, "~> 1.2", only: :dev},
{:phoenix_live_view, "~> 0.18.3"},
{:phoenix_live_view, "~> 0.18.0"},
{:phoenix_view, "~> 1.1"},
{:phoenix_live_dashboard, "~> 0.6"},
{:ecto_sql, "~> 3.6"},
{:postgrex, ">= 0.0.0"},
{:floki, ">= 0.30.0", only: :test},
{:phoenix_live_dashboard, "~> 0.7.0"},
{:oban, "~> 2.10"},
# {:esbuild, "~> 0.3", runtime: Mix.env() == :dev},
{:ex_doc, "~> 0.27", only: :dev, runtime: false},
{:swoosh, "~> 1.6"},
{:gen_smtp, "~> 1.0"},
{:phoenix_swoosh, "~> 1.0"},
{:oban, "~> 2.10"},
{:telemetry_metrics, "~> 0.6"},
{:telemetry_poller, "~> 1.0"},
{:gettext, "~> 0.18"},
{:jason, "~> 1.2"},
{:plug_cowboy, "~> 2.5"},
{:ecto_psql_extras, "~> 0.6"},
{:eqrcode, "~> 0.1.10"},
{:credo, "~> 1.5", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.0", only: [:dev, :test], runtime: false}
]

View File

@ -1,56 +1,53 @@
%{
"bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.1", "5114d780459a04f2b4aeef52307de23de961b69e13a5cd98a911e39fda13f420", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "42182d5f46764def15bf9af83739e3bf4ad22661b1c34fc3e88558efced07279"},
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
"castore": {:hex, :castore, "0.1.19", "a2c3e46d62b7f3aa2e6f88541c21d7400381e53704394462b9fd4f06f6d42bb6", [:mix], [], "hexpm", "e96e0161a5dc82ef441da24d5fa74aefc40d920f3a6645d15e1f9f3e66bb2109"},
"castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"},
"comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"},
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
"credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"},
"db_connection": {:hex, :db_connection, "2.4.2", "f92e79aff2375299a16bcb069a14ee8615c3414863a6fef93156aee8e86c2ff3", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4fe53ca91b99f55ea249693a0229356a08f4d1a7931d8ffa79289b145fe83668"},
"db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"},
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
"dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"},
"earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"},
"ecto": {:hex, :ecto, "3.9.1", "67173b1687afeb68ce805ee7420b4261649d5e2deed8fe5550df23bab0bc4396", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c80bb3d736648df790f7f92f81b36c922d9dd3203ca65be4ff01d067f54eb304"},
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.4", "5d43fd088d39a158c860b17e8d210669587f63ec89ea122a4654861c8c6e2db4", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.15.7", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "311db02f1b772e3d0dc7f56a05044b5e1499d78ed6abf38885e1ca70059449e5"},
"ecto_sql": {:hex, :ecto_sql, "3.9.0", "2bb21210a2a13317e098a420a8c1cc58b0c3421ab8e3acfa96417dab7817918c", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a8f3f720073b8b1ac4c978be25fa7960ed7fd44997420c304a4a2e200b596453"},
"elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"},
"ecto": {:hex, :ecto, "3.9.4", "3ee68e25dbe0c36f980f1ba5dd41ee0d3eb0873bccae8aeaf1a2647242bffa35", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de5f988c142a3aa4ec18b85a4ec34a2390b65b24f02385c1144252ff6ff8ee75"},
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.10", "e14d400930f401ca9f541b3349212634e44027d7f919bbb71224d7ac0d0e8acd", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.15.7 or ~> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "505e8cd81e4f17c090be0f99e92b1b3f0fd915f98e76965130b8ccfb891e7088"},
"ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"},
"elixir_make": {:hex, :elixir_make, "0.7.3", "c37fdae1b52d2cc51069713a58c2314877c1ad40800a57efb213f77b078a460d", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "24ada3e3996adbed1fa024ca14995ef2ba3d0d17b678b0f3f2b1f66e6ce2b274"},
"eqrcode": {:hex, :eqrcode, "0.1.10", "6294fece9d68ad64eef1c3c92cf111cfd6469f4fbf230a2d4cc905a682178f3f", [:mix], [], "hexpm", "da30e373c36a0fd37ab6f58664b16029919896d6c45a68a95cc4d713e81076f1"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"esbuild": {:hex, :esbuild, "0.4.0", "9f17db148aead4cf1e6e6a584214357287a93407b5fb51a031f122b61385d4c2", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "b61e4e6b92ffe45e4ee4755a22de6211a67c67987dc02afb35a425a0add1d447"},
"ex_doc": {:hex, :ex_doc, "0.29.0", "4a1cb903ce746aceef9c1f9ae8a6c12b742a5461e6959b9d3b24d813ffbea146", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "f096adb8bbca677d35d278223361c7792d496b3fc0d0224c9d4bc2f651af5db1"},
"ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"},
"expo": {:hex, :expo, "0.3.0", "13127c1d5f653b2927f2616a4c9ace5ae372efd67c7c2693b87fd0fdc30c6feb", [:mix], [], "hexpm", "fb3cd4bf012a77bc1608915497dae2ff684a06f0fa633c7afa90c4d72b881823"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"floki": {:hex, :floki, "0.34.0", "002d0cc194b48794d74711731db004fafeb328fe676976f160685262d43706a8", [:mix], [], "hexpm", "9c3a9f43f40dde00332a589bd9d389b90c1f518aef500364d00636acc5ebc99c"},
"gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"},
"gettext": {:hex, :gettext, "0.20.0", "75ad71de05f2ef56991dbae224d35c68b098dd0e26918def5bb45591d5c8d429", [:mix], [], "hexpm", "1c03b177435e93a47441d7f681a7040bd2a816ece9e2666d1c9001035121eb3d"},
"heex_formatter": {:git, "https://github.com/feliperenan/heex_formatter.git", "efa8f8092afae62d19128bf2bd9f1c0fb86e0b92", []},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"hut": {:hex, :hut, "1.3.0", "71f2f054e657c03f959cf1acc43f436ea87580696528ca2a55c8afb1b06c85e7", [:"erlang.mk", :rebar, :rebar3], [], "hexpm", "7e15d28555d8a1f2b5a3a931ec120af0753e4853a4c66053db354f35bf9ab563"},
"gettext": {:hex, :gettext, "0.22.0", "a25d71ec21b1848957d9207b81fd61cb25161688d282d58bdafef74c2270bdc4", [:mix], [{:expo, "~> 0.3.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "cb0675141576f73720c8e49b4f0fd3f2c69f0cd8c218202724d4aebab8c70ace"},
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
"oban": {:hex, :oban, "2.13.5", "6ba77f96bf8d8c57dd95c31292c76dd50104ac110c0bee8345cdf5e42f8afe89", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e5d93843377c7aa6417a6e89dfa63cb3043a4d959b9e946cb1d0018cafc0219b"},
"oban": {:hex, :oban, "2.13.6", "a0cb1bce3bd393770512231fb5a3695fa19fd3af10d7575bf73f837aee7abf43", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c1c5eb16f377b3cbbf2ea14be24d20e3d91285af9d1ac86260b7c2af5464887"},
"phoenix": {:hex, :phoenix, "1.6.15", "0a1d96bbc10747fd83525370d691953cdb6f3ccbac61aa01b4acb012474b047d", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d70ab9fbf6b394755ea88b644d34d79d8b146e490973151f248cacd122d20672"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
"phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.0", "4fe222c0be55fdc3f9c711e24955fc42a7cd9b7a2f5f406f2580a567c335a573", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "bebf0fc2d2113b61cb5968f585367234b7b4c21d963d691de7b4b2dc6cdaae6f"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.3", "2e3d009422addf8b15c3dccc65ce53baccbe26f7cfd21d264680b5867789a9c1", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c8845177a866e017dcb7083365393c8f00ab061b8b6b2bda575891079dce81b2"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.1", "b0bf8f3348dec4910907a2ad1453e642f6fe4d444376c1c9b26222d63c73cf97", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "b6c5d744bf4b40692b1b361d3608bdfd05aeab83e17c7bc217d730f007f31abf"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.2", "635cf07de947235deb030cd6b776c71a3b790ab04cebf526aa8c879fe17c7784", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "da287a77327e996cc166e4c440c3ad5ab33ccdb151b91c793209b39ebbce5b75"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.1.0", "f8e4780705c9f254cc853f7a40e25f7198ba4d91102bcfad2226669b69766b35", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "aa82f10afd9a4b6080fdf3274dbb9432b25b210d42b4b6b55308f6e59cd87c3d"},
"phoenix_template": {:hex, :phoenix_template, "1.0.0", "c57bc5044f25f007dc86ab21895688c098a9f846a8dda6bc40e2d0ddc146e38f", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "1b066f99a26fd22064c12b2600a9a6e56700f591bf7b20b418054ea38b4d4357"},
"phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"},
"phoenix_view": {:hex, :phoenix_view, "1.1.2", "1b82764a065fb41051637872c7bd07ed2fdb6f5c3bd89684d4dca6e10115c95a", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "7ae90ad27b09091266f6adbb61e1d2516a7c3d7062c6789d46a7554ec40f3a56"},
"plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"},
"plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"},
"plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"},
"postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"swoosh": {:hex, :swoosh, "1.8.2", "af9a22ab2c0d20b266f61acca737fa11a121902de9466a39e91bacdce012101c", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d058ba750eafadb6c09a84a352c14c5d1eeeda6e84945fcc95785b7f3067b7db"},
"swoosh": {:hex, :swoosh, "1.9.1", "0a5d7bf9954eb41d7e55525bc0940379982b090abbaef67cd8e1fd2ed7f8ca1a", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "76dffff3ffcab80f249d5937a592eaef7cc49ac6f4cdd27e622868326ed6371e"},
"table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"},
"telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
}

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

@ -0,0 +1,162 @@
## "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"
#: 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: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: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 ""
#: lib/memex_web/templates/user_settings/edit.html.heex:145
#, elixir-autogen, elixir-format
msgid "export data as json"
msgstr ""

View File

@ -0,0 +1,662 @@
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/live/invite_live/form_component.html.heex:20
#, elixir-autogen, elixir-format
msgid "Name"
msgstr ""
#: lib/memex_web/templates/layout/live.html.heex:40
#, 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/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:29
#, 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:32
#, 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/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/templates/user_registration/new.html.heex:32
#, elixir-autogen, elixir-format, fuzzy
msgid "language"
msgstr ""
#: lib/memex_web/components/user_card.ex:23
#, elixir-autogen, elixir-format, fuzzy
msgid "user confirmed on%{confirmed_datetime}"
msgstr ""
#: lib/memex_web/components/user_card.ex:34
#, elixir-autogen, elixir-format, fuzzy
msgid "user registered on%{registered_datetime}"
msgstr ""
#: lib/memex_web/components/invite_card.ex:22
#, elixir-autogen, elixir-format
msgid "uses left: %{uses_left}"
msgstr ""
#: lib/memex_web/components/invite_card.ex:27
#, elixir-autogen, elixir-format
msgid "uses left: unlimited"
msgstr ""

View File

@ -0,0 +1,92 @@
## "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"
#: 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

@ -0,0 +1,139 @@
## "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"
#: 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:139
#, elixir-autogen, elixir-format
msgid "did not change"
msgstr ""
#: lib/memex/accounts/user.ex:160
#, elixir-autogen, elixir-format
msgid "does not match password"
msgstr ""
#: lib/memex/accounts/user.ex:197
#, elixir-autogen, elixir-format
msgid "is not valid"
msgstr ""
#: lib/memex/accounts/user.ex:95
#, 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: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 ""
#: 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: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

@ -0,0 +1,158 @@
## "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"
#: 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_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:152
#, 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 ""
#: lib/memex_web/controllers/user_settings_controller.ex:65
#, elixir-autogen, elixir-format, fuzzy
msgid "language updated successfully."
msgstr ""

View File

@ -20,37 +20,17 @@ 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"
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
#: lib/memex_web/templates/layout/live.html.heex:40
#, elixir-autogen, elixir-format
msgid "Reconnecting..."
msgstr ""
@ -65,24 +45,14 @@ 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:"
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: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 ""
@ -107,8 +77,6 @@ msgstr ""
msgid "confirm new password"
msgstr ""
#: lib/memex_web/components/contexts_table_component.ex:48
#: lib/memex_web/components/notes_table_component.ex:48
#: lib/memex_web/live/note_live/form_component.html.heex:23
#, elixir-autogen, elixir-format
msgid "content"
@ -163,7 +131,7 @@ msgstr ""
msgid "email"
msgstr ""
#: lib/memex_web/components/user_card.ex:23
#: lib/memex_web/components/user_card.ex:29
#, elixir-autogen, elixir-format
msgid "email unconfirmed"
msgstr ""
@ -173,6 +141,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"
@ -198,7 +167,7 @@ msgstr ""
msgid "instance information"
msgstr ""
#: lib/memex_web/components/invite_card.ex:24
#: lib/memex_web/components/invite_card.ex:32
#, elixir-autogen, elixir-format
msgid "invite disabled"
msgstr ""
@ -306,17 +275,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 ""
@ -338,23 +307,13 @@ msgstr ""
msgid "tag1,tag2"
msgstr ""
#: lib/memex_web/components/contexts_table_component.ex:49
#: lib/memex_web/components/notes_table_component.ex:49
#: 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/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"
@ -370,8 +329,8 @@ msgstr ""
msgid "view the source code"
msgstr ""
#: lib/memex_web/components/contexts_table_component.ex:50
#: lib/memex_web/components/notes_table_component.ex:50
#: 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"
@ -466,7 +425,7 @@ msgstr ""
msgid "%{slug} could not be found"
msgstr ""
#: lib/memex_web/live/home_live.ex:13
#: lib/memex_web/live/home_live.ex:15
#, elixir-autogen, elixir-format
msgid "home"
msgstr ""
@ -486,6 +445,7 @@ msgstr ""
#: 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 ""
@ -500,7 +460,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 ""
@ -525,12 +485,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 ""
@ -634,3 +594,58 @@ msgstr ""
#, 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
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/templates/user_registration/new.html.heex:32
#, elixir-autogen, elixir-format
msgid "language"
msgstr ""
#: lib/memex_web/components/user_card.ex:23
#, elixir-autogen, elixir-format
msgid "user confirmed on%{confirmed_datetime}"
msgstr ""
#: lib/memex_web/components/user_card.ex:34
#, elixir-autogen, elixir-format
msgid "user registered on%{registered_datetime}"
msgstr ""
#: lib/memex_web/components/invite_card.ex:22
#, elixir-autogen, elixir-format
msgid "uses left: %{uses_left}"
msgstr ""
#: lib/memex_web/components/invite_card.ex:27
#, elixir-autogen, elixir-format
msgid "uses left: unlimited"
msgstr ""

View File

@ -30,23 +30,12 @@ msgstr ""
msgid "If you didn't create an account at %{url}, please ignore this."
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.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_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/accounts/email.ex:37
#, elixir-autogen, elixir-format
msgid "Reset your Memex password"
@ -57,12 +46,6 @@ msgstr ""
msgid "Update your Memex email"
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 ""
#: lib/memex_web/templates/email/update_email.html.heex:8
#: lib/memex_web/templates/email/update_email.txt.eex:4
#, elixir-autogen, elixir-format
@ -90,3 +73,20 @@ msgstr ""
#, 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

@ -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

@ -75,16 +75,6 @@ msgstr ""
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_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 +116,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 +132,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 +146,13 @@ 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 ""
#: lib/memex_web/controllers/user_settings_controller.ex:65
#, elixir-autogen, elixir-format
msgid "language updated successfully."
msgstr ""

View File

@ -0,0 +1,56 @@
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

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

View File

@ -17,6 +17,36 @@ defmodule Memex.ContextsTest do
assert Contexts.list_contexts(user) == [context_a, context_b, context_c]
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)
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)
@ -24,6 +54,51 @@ defmodule Memex.ContextsTest do
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

View File

@ -14,9 +14,41 @@ defmodule Memex.NotesTest 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())
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)
@ -24,6 +56,51 @@ 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)
assert Notes.get_note!(note.id, user) == note

View File

@ -17,6 +17,36 @@ defmodule Memex.PipelinesTest do
assert Pipelines.list_pipelines(user) == [pipeline_a, pipeline_b, pipeline_c]
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)
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)
@ -24,6 +54,51 @@ defmodule Memex.PipelinesTest do
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

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
}
@ -34,7 +34,15 @@ defmodule MemexWeb.ContextLiveTest do
{:ok, _index_live, html} = live(conn, Routes.context_index_path(conn, :index))
assert html =~ "contexts"
assert html =~ context.content
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
@ -56,7 +64,7 @@ defmodule MemexWeb.ContextLiveTest do
|> follow_redirect(conn, Routes.context_index_path(conn, :index))
assert html =~ "#{@create_attrs |> Map.get("slug")} created"
assert html =~ "some content"
assert html =~ "some-slug"
end
test "updates context in listing", %{conn: conn, context: context} do
@ -78,7 +86,7 @@ defmodule MemexWeb.ContextLiveTest do
|> follow_redirect(conn, Routes.context_index_path(conn, :index))
assert html =~ "#{@update_attrs |> Map.get("slug")} saved"
assert html =~ "some updated content"
assert html =~ "some-updated-slug"
end
test "deletes context in listing", %{conn: conn, context: context} do
@ -96,7 +104,7 @@ defmodule MemexWeb.ContextLiveTest do
{:ok, _show_live, html} = live(conn, Routes.context_show_path(conn, :show, context.slug))
assert html =~ "context"
assert html =~ context.content
assert html =~ context.slug
end
test "updates context within modal", %{conn: conn, context: context} do
@ -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
@ -117,7 +129,7 @@ defmodule MemexWeb.ContextLiveTest do
|> follow_redirect(conn, Routes.context_show_path(conn, :show, context.slug))
assert html =~ "#{context.slug} saved"
assert html =~ "some updated content"
assert html =~ "tag2"
end
test "deletes context", %{conn: conn, context: context} do
@ -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
}
@ -34,7 +35,15 @@ defmodule MemexWeb.NoteLiveTest do
{:ok, _index_live, html} = live(conn, Routes.note_index_path(conn, :index))
assert html =~ "notes"
assert html =~ note.content
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
@ -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
@ -56,7 +69,7 @@ defmodule MemexWeb.NoteLiveTest do
|> follow_redirect(conn, Routes.note_index_path(conn, :index))
assert html =~ "#{@create_attrs |> Map.get("slug")} created"
assert html =~ "some content"
assert html =~ "some-slug"
end
test "updates note in listing", %{conn: conn, note: note} do
@ -78,7 +91,7 @@ defmodule MemexWeb.NoteLiveTest do
|> follow_redirect(conn, Routes.note_index_path(conn, :index))
assert html =~ "#{@update_attrs |> Map.get("slug")} saved"
assert html =~ "some updated content"
assert html =~ "some-updated-slug"
end
test "deletes note in listing", %{conn: conn, note: note} do
@ -96,7 +109,7 @@ defmodule MemexWeb.NoteLiveTest do
{:ok, _show_live, html} = live(conn, Routes.note_show_path(conn, :show, note.slug))
assert html =~ "note"
assert html =~ note.content
assert html =~ note.slug
end
test "updates note within modal", %{conn: conn, note: note} do
@ -117,7 +130,7 @@ defmodule MemexWeb.NoteLiveTest do
|> follow_redirect(conn, Routes.note_show_path(conn, :show, note.slug))
assert html =~ "#{note.slug} saved"
assert html =~ "some updated content"
assert html =~ "tag2"
end
test "deletes note", %{conn: conn, note: note} do
@ -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",
tag: [],
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",
tag: [],
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",
tag: [],
tags: ["example-tag"],
slug: random_slug(),
visibility: :private
})
|> Pipelines.create_pipeline(user)
pipeline
%{pipeline | tags_string: nil}
end
end