~mlb/linkhut

f2536ee1ebcff45149e063c451e75954eb353580 — Matías Larre Borges a month ago 2d3d741
Add exploration links
M assets/css/_svgs.scss => assets/css/_svgs.scss +6 -1
@@ 13,4 13,9 @@
// icon for to-read links
@function later($color) {
  @return inline-svg("<?xml version='1.0' encoding='UTF-8'?><svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='#{$color}'><path d='M12,2C6.477,2,2,6.477,2,12c0,5.523,4.477,10,10,10s10-4.477,10-10C22,6.477,17.523,2,12,2z M14.586,16l-3.293-3.293 C11.105,12.519,11,12.265,11,12V7c0-0.552,0.448-1,1-1h0c0.552,0,1,0.448,1,1v4.586l3,3c0.39,0.39,0.39,1.024,0,1.414l0,0 C15.61,16.39,14.976,16.39,14.586,16z'/></svg>")
}
\ No newline at end of file
}

// icon for rss feed
@function rss($color) {
  @return inline-svg("<?xml version='1.0' encoding='UTF-8'?><svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256' fill='#{$color}'><circle cx='68' cy='189' r='24'></circle><path d='M160 213h-34a82 82 0 0 0 -82 -82v-34a116 116 0 0 1 116 116z'></path><path d='M184 213A140 140 0 0 0 44 73 V 38a175 175 0 0 1 175 175z'></path></svg>")
}

M assets/css/app.scss => assets/css/app.scss +39 -5
@@ 365,6 365,7 @@ nav {
    & > div.breadcrumbs {
        display: inline-flex;
        flex-grow: 0;
        flex-wrap: wrap;

        & > a.brand {
            color: $foreground;


@@ 546,7 547,37 @@ div#content, div#controls {
                    content: ", ";
                }
            }
        }

        & div.explore {
            & > ul {
                align-items: baseline;
                display: inline-flex;
                flex-direction: row;
                font-size: $font-small;
                list-style-type: none;
                margin: 0;
                padding: 0;

                & > li {
                    align-items: baseline;
                    display: inline-flex;
                    list-style: none outside none;

                    & > a {
                        text-transform: lowercase;
                    }

                    &:not(:first-child) {
                        margin-left: $spacing-tiny;
                    }

                    &:not(:first-child)::before {
                        content: "\00B7";
                        margin-right: $spacing-tiny;
                    }
                }
            }
        }
    }



@@ 906,11 937,14 @@ div.pagination {
    }
}

a.feed-link > svg {
    display: inline;
    height: 1.1em;
    vertical-align: bottom;
    width: 1.1em;
a.feed-link {
    &::before {
        content: rss($near-black);
        display: inline-block;
        vertical-align: middle;
        width: $font-normal;
        margin-right: 0.5 * $spacing-tiny;
    }
}

details {

M lib/linkhut/links.ex => lib/linkhut/links.ex +42 -1
@@ 204,7 204,11 @@ defmodule Linkhut.Links do
    datetime = DateTime.add(DateTime.now!("Etc/UTC"), -days, :day)

    links()
    |> join(:inner, [l, _], u in assoc(l, :user))
    |> join(:inner, [l], u in assoc(l, :user))
    |> join(:left, [l], latest in subquery(latest()),
      on: l.url == latest.url and l.inserted_at == latest.inserted_at
    )
    |> where([l, _, _, latest], l.url == latest.url and l.inserted_at == latest.inserted_at)
    |> where(is_private: false)
    |> where(is_unread: false)
    |> where([l], l.inserted_at >= ^datetime)


@@ 213,6 217,27 @@ defmodule Linkhut.Links do
  end

  @doc """
  Returns the most popular public links
  """
  def popular(popularity \\ 3) do
    links()
    |> join(:inner, [l, _], u in assoc(l, :user))
    |> join(:left, [l, _, _], earliest in subquery(earliest()),
      on: l.url == earliest.url and l.inserted_at == earliest.inserted_at
    )
    |> where([l, s], s.savers > 1)
    |> where([l, _, _, latest], l.url == latest.url and l.inserted_at == latest.inserted_at)
    |> where(is_private: false)
    |> where(is_unread: false)
    |> where(
      [l, s, _, latest],
      s.savers > ^popularity and l.url == latest.url and l.inserted_at == latest.inserted_at
    )
    |> order_by([l, s, _, _], desc: s.savers, desc: l.inserted_at)
    |> preload([_, _, u], user: u)
  end

  @doc """
  Returns the unread links for a user
  """
  def unread(user_id) do


@@ 254,4 279,20 @@ defmodule Linkhut.Links do
      }
    )
  end

  defp earliest() do
    Link
    |> where(is_private: false)
    |> where(is_unread: false)
    |> group_by([x], x.url)
    |> select([x], %{url: x.url, inserted_at: min(x.inserted_at)})
  end

  defp latest() do
    Link
    |> where(is_private: false)
    |> where(is_unread: false)
    |> group_by([x], x.url)
    |> select([x], %{url: x.url, inserted_at: max(x.inserted_at)})
  end
end

M lib/linkhut/links/link.ex => lib/linkhut/links/link.ex +19 -4
@@ 49,15 49,30 @@ defmodule Linkhut.Links.Link do

  defp update_tags(changeset) do
    case get_change(changeset, :tags) do
      nil -> changeset
      tags -> if Enum.any?(tags, &Tags.is_unread?/1), do: force_change(changeset, :is_unread, true), else: changeset
      nil ->
        changeset

      tags ->
        if Enum.any?(tags, &Tags.is_unread?/1),
          do: force_change(changeset, :is_unread, true),
          else: changeset
    end
  end

  defp dedupe_tags(changeset) do
    case get_change(changeset, :tags) do
      nil -> changeset
      tags -> force_change(changeset, :tags, Enum.uniq_by(tags, &(if Tags.is_unread?(&1), do: Tags.unread, else: String.downcase(&1))))
      nil ->
        changeset

      tags ->
        force_change(
          changeset,
          :tags,
          Enum.uniq_by(
            tags,
            &if(Tags.is_unread?(&1), do: Tags.unread(), else: String.downcase(&1))
          )
        )
    end
  end


M lib/linkhut_web/controllers/link_controller.ex => lib/linkhut_web/controllers/link_controller.ex +17 -9
@@ 114,7 114,7 @@ defmodule LinkhutWeb.LinkController do
    cond do
      has_query?(params) -> query(conn, context(params), Map.get(params, "query"), page(params))
      has_filters?(params) -> filter(conn, context(params), page(params))
      true -> recent(conn, page(params))
      true -> explore(conn, view(params), page(params))
    end
  end



@@ 125,22 125,26 @@ defmodule LinkhutWeb.LinkController do

    conn
    |> render("index.html",
         links: realize_query(links_query, page),
         tags: Tags.for_query(links_query, limit: @related_tags_limit),
         query: "",
         context: context(params),
         title: :unread
      links: realize_query(links_query, page),
      tags: Tags.for_query(links_query, limit: @related_tags_limit),
      query: "",
      context: context(params),
      title: :unread
    )
  end

  defp recent(conn, page) do
  defp explore(conn, view, page) do
    context =
      case conn.assigns[:current_user] do
        nil -> %Context{}
        current_user -> %Context{visible_as: current_user.username}
      end

    links_query = Links.recent()
    links_query =
      case view do
        :recent -> Links.recent()
        :popular -> Links.popular()
      end

    conn
    |> render(:index,


@@ 148,7 152,7 @@ defmodule LinkhutWeb.LinkController do
      tags: Tags.for_query(links_query, limit: @related_tags_limit),
      query: "",
      context: context,
      title: :recent
      title: view
    )
  end



@@ 235,4 239,8 @@ defmodule LinkhutWeb.LinkController do

  defp page(%{"p" => page}), do: page
  defp page(_), do: 1

  defp view(%{"v" => "recent"}), do: :recent
  defp view(%{"v" => "popular"}), do: :popular
  defp view(_), do: :recent
end

M lib/linkhut_web/templates/layout/_context.html.heex => lib/linkhut_web/templates/layout/_context.html.heex +1 -1
@@ 24,7 24,7 @@
              <ul class="tags">
                <%= for tag <- @context.tagged_with do %>
                  <li><a href={Routes.tags_path(@conn, :show, [tag])}><%= tag %></a></li>
                 <% end %>
                <% end %>
              </ul>
            <% end %>
          <% _ -> %>

M lib/linkhut_web/templates/layout/_session.html.heex => lib/linkhut_web/templates/layout/_session.html.heex +4 -2
@@ 3,12 3,14 @@
    <li>
      <span>Logged in as <%= link(@current_user.username, to: Routes.user_path(@conn, :show, @current_user.username)) %></span>
      <%= if @unread_count > 0 do %>
        <ul class="quicklinks"><li><%= link("Unread", to: Routes.unread_path(@conn, :unread), title: gettext("%{count} unread links", count: @unread_count), data: [count: @unread_count]) %></li></ul>
        <ul class="quicklinks">
          <li><%= link("Unread", to: Routes.unread_path(@conn, :unread), title: gettext("%{count} unread links", count: @unread_count), data: [count: @unread_count]) %></li>
        </ul>
      <% end %>
    </li>
    <li><%= link("Settings", to: Routes.profile_path(@conn, :show)) %></li>
    <li>
      <%# Using a form instead of a link, since the route for signing out requires making a DELETE call and we don't want to rely on JS being activated %>
      
      <%= form_for :nil, Routes.session_path(@conn, :delete), [method: :delete, itemprop: "signout"], fn _f -> %>
        <%= submit("Log out") %>
      <% end %>

M lib/linkhut_web/templates/link/_bookmark.html.heex => lib/linkhut_web/templates/link/_bookmark.html.heex +77 -73
@@ 1,83 1,87 @@
<div class="bookmark-card">
<div class="bookmark">
  <div data-posted-on={@link.inserted_at} data-savers={@link.savers} class="title">
    <h4><a class="taggedlink" href={@link.url}><%= @link.title %></a></h4>
    <%= if @link.is_private do %>
      <span class="no-css-label"><%= gettext("Private") %></span>
    <% end %>
    <%= if @context.url == nil and not (@link.is_private or @link.is_unread) and @link.savers > 1 do %>
      <a class="savers" data-count={@link.savers} data-label={gettext("people")} href={Routes.bookmark_path(@conn, :show, @link.url)}></a>
    <% end %>
  </div>
  <div class="full-url">
    <a rel="nofollow" href={@link.url}><%= @link.url %></a>
  </div>
  <div class="description">
    <%= sanitize(Earmark.as_html!(@link.notes, pure_links: false)) %>
  </div>
  <div class="ownership">
    <%= unless @logged_in? && @link.user_id == @current_user.id do %>
  <div class="bookmark">
    <div data-posted-on={@link.inserted_at} data-savers={@link.savers} class="title">
      <h4><a class="taggedlink" href={@link.url}><%= @link.title %></a></h4>
      <%= if @link.is_private do %>
        <span class="no-css-label"><%= gettext("Private") %></span>
      <% end %>
      <%= if @context.url == nil and not (@link.is_private or @link.is_unread) and @link.savers > 1 do %>
        <a class="savers" data-count={@link.savers} data-label={gettext("people")} href={Routes.bookmark_path(@conn, :show, @link.url)}></a>
      <% end %>
    </div>
    <div class="full-url">
      <a rel="nofollow" href={@link.url}><%= @link.url %></a>
    </div>
    <div class="description">
      <%= sanitize(Earmark.as_html!(@link.notes, pure_links: false)) %>
    </div>
    <div class="ownership">
      <%= unless @logged_in? && @link.user_id == @current_user.id do %>
        <span>
          <%= gettext("by") %> <a href={current_path(@conn, username: @link.user.username)}><%= @link.user.username %></a>
        </span>
      <% end %>
      <span>
        <%= gettext("by") %> <a href={current_path(@conn, username: @link.user.username)}><%= @link.user.username %></a>
      </span>
    <% end %>
    <span>
      <%= Timex.format!(@link.inserted_at, "{relative}", :relative) %>
    </span>
    <%= if @context.url == nil and not (@link.is_private or @link.is_unread) and @link.savers > 1 do %>
      <span class="savers">
        <%= gettext("saved") %> <a href={Routes.bookmark_path(@conn, :show, @link.url)}><%= @link.savers %></a> <%= ngettext("time", "times", @link.savers) %>
        <%= Timex.format!(@link.inserted_at, "{relative}", :relative) %>
      </span>
    <% end %>
  </div>
  <div class="meta">
    <div class="tags">
      <h5 class="label"><%= gettext("Tags:") %></h5>
      <ul class="tags" data-label={gettext("tags")}>
        <%= for tag <- @link.tags do %>
          <li>
            <a href={current_path(@conn, tag: tag)}><%= tag %></a>
          </li>
        <% end %>
      </ul>
      <%= if @context.url == nil and not (@link.is_private or @link.is_unread) and @link.savers > 1 do %>
        <span class="savers">
          <%= gettext("saved") %> <a href={Routes.bookmark_path(@conn, :show, @link.url)}><%= @link.savers %></a> <%= ngettext("time", "times", @link.savers) %>
        </span>
      <% end %>
    </div>
    <%= if @logged_in? do %>
      <div class="actions">
        <h5 class="label"><%= gettext("Actions:") %></h5>
        <ul class="actions">
          <%= if !assigns[:hide_actions] do %>
            <%= if @link.user_id == @current_user.id do %>
              <li>
                <a href={Routes.link_path(@conn, :edit, url: @link.url)}><%= gettext("edit") %></a>
              </li>
              <li>
                <a href={Routes.link_path(@conn, :delete, url: @link.url)}><%= gettext("delete") %></a>
              </li>
              <%= if @link.is_unread do %>
              <li>
                <%= form_for :mark_as_read, Routes.link_path(@conn, :update), [method: :put], fn _ -> %>
                  <%= hidden_input(:link, :url, value: @link.url) %>
                  <%= hidden_input(:link, :is_unread, value: false) %>
                  <%= submit(gettext("mark as read")) %>
                <% end %>
              </li>
              <% end %>
            <% else %>
              <li>
                <a href={Routes.link_path(@conn, :new, url: @link.url, title: @link.title, notes: @link.notes, tags: @link.tags)}><%= gettext("copy to mine") %></a>
              </li>
            <% end %>
    <div class="meta">
      <div class="tags">
        <h5 class="label"><%= gettext("Tags:") %></h5>
        <ul class="tags" data-label={gettext("tags")}>
          <%= for tag <- @link.tags do %>
            <li>
              <a href={current_path(@conn, tag: tag)}><%= tag %></a>
            </li>
          <% end %>
        </ul>
      </div>
    <% end %>
      <%= if @logged_in? do %>
        <div class="actions">
          <h5 class="label"><%= gettext("Actions:") %></h5>
          <ul class="actions">
            <%= if !assigns[:hide_actions] do %>
              <%= if @link.user_id == @current_user.id do %>
                <li>
                  <a href={Routes.link_path(@conn, :edit, url: @link.url)}><%= gettext("edit") %></a>
                </li>
                <li>
                  <a href={Routes.link_path(@conn, :delete, url: @link.url)}><%= gettext("delete") %></a>
                </li>
                <%= if @link.is_unread do %>
                  <li>
                    <%= form_for :mark_as_read, Routes.link_path(@conn, :update), [method: :put], fn _ -> %>
                      <%= hidden_input(:link, :url, value: @link.url) %>
                      <%= hidden_input(:link, :is_unread, value: false) %>
                      <%= submit(gettext("mark as read")) %>
                    <% end %>
                  </li>
                <% end %>
              <% else %>
                <li>
                  <a href={Routes.link_path(@conn, :new, url: @link.url, title: @link.title, notes: @link.notes, tags: @link.tags)}><%= gettext("copy to mine") %></a>
                </li>
              <% end %>
            <% end %>
          </ul>
        </div>
      <% end %>
    </div>
  </div>
</div>
<%= if @link.is_private or @link.is_unread do %>
<div class="icons">
    <%= if @link.is_private do %><span data-icon-type="private" title={gettext("private")}></span><% end %>
    <%= if @link.is_unread do %><span data-icon-type="unread" title={gettext("unread")}></span><% end %>
</div>
<% end %>
  <%= if @link.is_private or @link.is_unread do %>
    <div class="icons">
      <%= if @link.is_private do %>
        <span data-icon-type="private" title={gettext("private")}></span>
      <% end %>
      <%= if @link.is_unread do %>
        <span data-icon-type="unread" title={gettext("unread")}></span>
      <% end %>
    </div>
  <% end %>
</div>
<hr />

M lib/linkhut_web/templates/link/_pagination.html.heex => lib/linkhut_web/templates/link/_pagination.html.heex +42 -43
@@ 5,49 5,48 @@
      <span class="no-css-label"><%= gettext("Previous") %></span>
    </a>
  <% end %>
    <% pages = ceil(@page.count/LinkhutWeb.LinkController.links_per_page) %>
    <%= cond do %>
        <% pages <= 1 -> %>
            <%# noop %>
        <% pages <= 7 -> %>
            <%= for p <- 1..pages do %>
                <a class={if p == @page.page,  do: "active"} href={current_path(@conn, page: p)}>
                    <span><%= p %></span>
                </a>
            <% end %>
        <% @page.page <= 3 or @page.page >= (pages-2) -> %>
            <%= for p <- 1..3 do %>
                <a class={if p == @page.page,  do: "active"} href={current_path(@conn, page: p)}>
                    <span><%= p %></span>
                </a>
            <% end %>
            <span>
                <span>&hellip;</span>
            </span>
            <%= for p <- (pages-2)..pages do %>
                <a class={if p == @page.page,  do: "active"} href={current_path(@conn, page: p)}>
                    <span><%= p %></span>
                </a>
            <% end %>
        <% true -> %>
            <a href={current_path(@conn, page: 1)}>
                <span>1</span>
            </a>
            <span>
                <span>&hellip;</span>
            </span>
            <%= for p <- (@page.page-1)..(@page.page+1) do %>
                <a class={if p == @page.page,  do: "active"} href={current_path(@conn, page: p)}>
                    <span><%= p %></span>
                </a>
            <% end %>
            <span>
                <span>&hellip;</span>
            </span>
            <a href={current_path(@conn, page: pages)}>
                <span><%= pages %></span>
            </a>
    <% end %>
  <% pages = ceil(@page.count / LinkhutWeb.LinkController.links_per_page()) %>
  <%= cond do %>
    <% pages <= 1 -> %>
    <% pages <= 7 -> %>
      <%= for p <- 1..pages do %>
        <a class={if p == @page.page, do: "active"} href={current_path(@conn, page: p)}>
          <span><%= p %></span>
        </a>
      <% end %>
    <% @page.page <= 3 or @page.page >= (pages-2) -> %>
      <%= for p <- 1..3 do %>
        <a class={if p == @page.page, do: "active"} href={current_path(@conn, page: p)}>
          <span><%= p %></span>
        </a>
      <% end %>
      <span>
        <span>&hellip;</span>
      </span>
      <%= for p <- (pages-2)..pages do %>
        <a class={if p == @page.page, do: "active"} href={current_path(@conn, page: p)}>
          <span><%= p %></span>
        </a>
      <% end %>
    <% true -> %>
      <a href={current_path(@conn, page: 1)}>
        <span>1</span>
      </a>
      <span>
        <span>&hellip;</span>
      </span>
      <%= for p <- (@page.page-1)..(@page.page+1) do %>
        <a class={if p == @page.page, do: "active"} href={current_path(@conn, page: p)}>
          <span><%= p %></span>
        </a>
      <% end %>
      <span>
        <span>&hellip;</span>
      </span>
      <a href={current_path(@conn, page: pages)}>
        <span><%= pages %></span>
      </a>
  <% end %>
  <%= if @page.has_next do %>
    <a href={current_path(@conn, page: @page.next_page)}>
      <span class="no-css-label"><%= gettext("Next") %></span>

M lib/linkhut_web/templates/link/_side-pannel.html.heex => lib/linkhut_web/templates/link/_side-pannel.html.heex +11 -9
@@ 7,7 7,7 @@
  <% end %>
  <%= if length(@tags) > 0 do %>
    <section>
      <h5><%= gettext("tags") %></h5>
      <h5><%= gettext("Tags") %></h5>
      <ul class="tag-cloud">
        <%= for %{tag: tag, count: count} <- @tags do %>
          <li><a href={current_path(@conn, tag: tag)} data-count={count}><%= tag %></a></li>


@@ 16,15 16,17 @@
    </section>
  <% end %>
  <section>
    <div class="explore">
      <h5><%= gettext("Explore") %></h5>
      <ul>
        <li><a href={Routes.link_path(@conn, :show, v: "recent")}><%= gettext("Recent") %></a></li>
        <li><a href={Routes.link_path(@conn, :show, v: "popular")}><%= gettext("Popular") %></a></li>
      </ul>
    </div>
  </section>
  <section>
    <h5>
      <a class="feed-link" href={feed_path(@conn)}>
        <svg data-icon="rss-feed" width="16" height="16" viewBox="0 0 256 256">
          <circle cx="68" cy="189" r="24" />
          <path d="M160 213h-34a82 82 0 0 0 -82 -82v-34a116 116 0 0 1 116 116z" />
          <path d="M184 213A140 140 0 0 0 44 73 V 38a175 175 0 0 1 175 175z" />
        </svg>
        <%= gettext("RSS feed") %>
      </a>
      <a class="feed-link" href={feed_path(@conn)}><%= gettext("RSS feed") %></a>
    </h5>
  </section>
</aside>

M lib/linkhut_web/templates/link/delete.html.heex => lib/linkhut_web/templates/link/delete.html.heex +1 -1
@@ 11,7 11,7 @@
      <%= sanitize(Earmark.as_html!(@link.notes, pure_links: false)) %>
    </div>
  </div>
  <hr>
  <hr />
  <%= form_for @changeset, Routes.link_path(@conn, :delete), fn f -> %>
    <fieldset>
      <%= hidden_input(f, :url) %>

M lib/linkhut_web/templates/settings/oauth/authorization/new.html.heex => lib/linkhut_web/templates/settings/oauth/authorization/new.html.heex +1 -2
@@ 5,8 5,7 @@
    <strong><%= @client.name %></strong> would like access to your linkhut account.
  </p>
  <p>
    <strong><%= @client.name %></strong>
    is a third-party application operated by <strong><%= @client.owner.username %></strong>. You may revoke this access at any time.
    <strong><%= @client.name %></strong> is a third-party application operated by <strong><%= @client.owner.username %></strong>. You may revoke this access at any time.
  </p>
  They would like permission to access the following resources on your account:
  <ul>

M lib/linkhut_web/views/link_view.ex => lib/linkhut_web/views/link_view.ex +25 -6
@@ 31,7 31,11 @@ defmodule LinkhutWeb.LinkView do
  def current_path(conn, opts)

  def current_path(%Plug.Conn{query_params: params} = conn, username: username) do
    current_path(conn, %Context{Map.drop(context(conn), [:url]) | from: Accounts.get_user(username)}, Map.drop(params, ["p"]))
    current_path(
      conn,
      %Context{Map.drop(context(conn), [:url]) | from: Accounts.get_user(username)},
      Map.drop(params, ["p"])
    )
  end

  def current_path(%Plug.Conn{query_params: params} = conn, url: url) do


@@ 135,20 139,35 @@ defmodule LinkhutWeb.LinkView do

  defp title(%Context{} = context) do
    case context do
      %{from: user, url: url, tagged_with: tags} when not is_nil(user) and is_binary(url) and tags != [] ->
        LinkhutWeb.Gettext.gettext("Bookmarks for url: %{url} by linkhut user: %{user} tagged with: %{tags}", url: url, tags: Enum.join(tags, ","), user: user.username)
      %{from: user, url: url, tagged_with: tags}
      when not is_nil(user) and is_binary(url) and tags != [] ->
        LinkhutWeb.Gettext.gettext(
          "Bookmarks for url: %{url} by linkhut user: %{user} tagged with: %{tags}",
          url: url,
          tags: Enum.join(tags, ","),
          user: user.username
        )

      %{from: user, url: url} when not is_nil(user) and is_binary(url) ->
        LinkhutWeb.Gettext.gettext("Bookmarks for url: %{url} by linkhut user: %{user}", url: url, user: user.username)
        LinkhutWeb.Gettext.gettext("Bookmarks for url: %{url} by linkhut user: %{user}",
          url: url,
          user: user.username
        )

      %{url: url, tagged_with: tags} when is_binary(url) and tags != [] ->
        LinkhutWeb.Gettext.gettext("Bookmarks for url: %{url} tagged with: %{tags}", url: url, tags: Enum.join(tags, ","))
        LinkhutWeb.Gettext.gettext("Bookmarks for url: %{url} tagged with: %{tags}",
          url: url,
          tags: Enum.join(tags, ",")
        )

      %{url: url} when is_binary(url) ->
        LinkhutWeb.Gettext.gettext("Bookmarks for url: %{url}", url: url)

      %{from: user, tagged_with: tags} when not is_nil(user) and tags != [] ->
        LinkhutWeb.Gettext.gettext("Bookmarks by linkhut user: %{user} tagged with: %{tags}", tags: Enum.join(tags, ","), user: user.username)
        LinkhutWeb.Gettext.gettext("Bookmarks by linkhut user: %{user} tagged with: %{tags}",
          tags: Enum.join(tags, ","),
          user: user.username
        )

      %{from: user} when not is_nil(user) ->
        LinkhutWeb.Gettext.gettext("Bookmarks by linkhut user: %{user}", user: user.username)

M priv/repo/migrations/20221214091713_add_is_unread_to_links.exs => priv/repo/migrations/20221214091713_add_is_unread_to_links.exs +8 -4
@@ 9,14 9,18 @@ defmodule Linkhut.Repo.Migrations.AddIsUnreadToLinks do

    create index(:links, [:is_unread])

    if (direction() == :up) do
    if direction() == :up do
      flush()

      execute(&execute_up/0)
    end
  end


  defp execute_up, do: from(l in Linkhut.Links.Link, where: fragment("lower(tags::text)::text[] && ARRAY['unread','toread']"), update: [set: [is_unread: true]]) |> Linkhut.Repo.update_all([])

  defp execute_up do
    from(l in Linkhut.Links.Link,
      where: fragment("lower(tags::text)::text[] && ARRAY['unread','toread']"),
      update: [set: [is_unread: true]]
    )
    |> Linkhut.Repo.update_all([])
  end
end