Compare commits

...

8 Commits

  1. 23
      CHANGELOG.md
  2. 23
      assets/js/app.js
  3. 21
      assets/js/hooks.js
  4. 4
      lib/mirage_web/live/link_live/show.ex
  5. 2
      lib/mirage_web/live/link_live/show.html.leex
  6. 1
      lib/mirage_web/live/live_helpers.ex
  7. 18
      lib/mirage_web/live/modal_component.ex
  8. 12
      lib/mirage_web/live/modal_component.html.leex
  9. 64
      lib/mirage_web/live/note_live/form_component.ex
  10. 20
      lib/mirage_web/live/note_live/form_component.html.leex
  11. 41
      lib/mirage_web/live/note_live/index.ex
  12. 50
      lib/mirage_web/live/note_live/index.html.leex
  13. 71
      lib/mirage_web/live/note_live/show.ex
  14. 75
      lib/mirage_web/live/note_live/show.html.leex
  15. 8
      lib/mirage_web/router.ex
  16. 26
      lib/mirage_web/templates/note/_note.html.eex
  17. 7
      lib/mirage_web/templates/note/edit.html.eex
  18. 15
      lib/mirage_web/templates/note/index.html.eex
  19. 5
      lib/mirage_web/templates/note/new.html.eex
  20. 28
      lib/mirage_web/templates/topic/show.html.eex
  21. 3
      lib/mirage_web/views/note_view.ex
  22. 2
      mix.exs

23
CHANGELOG.md

@ -5,6 +5,29 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline
<!-- changelog -->
## [v0.48.0](https://git.inhji.de/inhji/mirage/compare/v0.47.0...v0.48.0) (2021-02-21)
### Features:
* add hook and phx_ignore to note form
* add crosslinks and tags to note live
* add hooks.js, add NoteForm hook to load codemirror
* WIP transition notes to live_view
### Bug Fixes:
* clean up labels
* topic note list
* p key not triggering
## [v0.47.0](https://git.inhji.de/inhji/mirage/compare/v0.46.1...v0.47.0) (2021-02-20)

23
assets/js/app.js

@ -18,17 +18,9 @@ import {Socket} from "phoenix"
import NProgress from "nprogress"
import {LiveSocket} from "phoenix_live_view"
import TableSort from "tablesort"
import Hooks from "./hooks"
const handledKeys = ["p"]
const Hooks = {}
Hooks.GotoAnything = {
updated() {
if (this.el.classList.contains("open")) {
const input = document.querySelector("#query")
input.focus()
}
}
}
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {
@ -36,7 +28,7 @@ let liveSocket = new LiveSocket("/live", Socket, {
hooks: Hooks,
metadata: {
keydown: (e, el) => {
if (handledKeys.includes(e.key))
if (handledKeys.includes(e.key) && e.ctrlKey)
e.preventDefault()
return {
@ -60,16 +52,7 @@ liveSocket.connect()
// >> liveSocket.disableLatencySim()
window.liveSocket = liveSocket
const element = "#editor"
if (document.querySelector(element)) {
import(/* webpackChunkName: "editor" */ "./editor.js").then(module => {
const initEditor = module.default
initEditor(element)
})
}
const tables = "table"
document.querySelectorAll(tables).forEach(table => {
document.querySelectorAll("table").forEach(table => {
new TableSort(table)
})

21
assets/js/hooks.js

@ -0,0 +1,21 @@
const Hooks = {}
Hooks.GotoAnything = {
updated() {
if (this.el.classList.contains("open")) {
const input = document.querySelector("#query")
input.focus()
}
}
}
Hooks.NoteForm = {
mounted() {
if (document.querySelector("#editor")) {
import(/* webpackChunkName: "editor" */ "./editor.js").then(module => {
const initEditor = module.default
initEditor("#editor")
})
}
}
}
export default Hooks

4
lib/mirage_web/live/link_live/show.ex

@ -4,8 +4,8 @@ defmodule MirageWeb.LinkLive.Show do
alias Mirage.Links
@impl true
def mount(_params, _session, socket) do
{:ok, socket}
def mount(_params, %{"user_id" => user_id}, socket) do
{:ok, socket |> assign(:current_user, Mirage.Accounts.get_user!(user_id))}
end
@impl true

2
lib/mirage_web/live/link_live/show.html.leex

@ -15,8 +15,10 @@
<%= link @link.url, to: @link.url, target: "_blank" %>
</p>
<%= if @current_user do %>
<div class="buttons">
<%= live_patch "Edit", to: Routes.link_show_path(@socket, :edit, @link), class: "button", class: "button" %>
<%= live_redirect "Back", to: Routes.link_index_path(@socket, :index), class: "button" %>
<%= link "Delete", to: "#", phx_click: "delete", phx_value_id: @link.id, data: [confirm: "Are you sure?"], class: "button" %>
</div>
<% end %>

1
lib/mirage_web/live/live_helpers.ex

@ -18,6 +18,7 @@ defmodule MirageWeb.LiveHelpers do
def live_modal(socket, component, opts) do
path = Keyword.fetch!(opts, :return_to)
modal_opts = [id: :modal, return_to: path, component: component, opts: opts]
live_component(socket, MirageWeb.ModalComponent, modal_opts)
end
end

18
lib/mirage_web/live/modal_component.ex

@ -1,24 +1,6 @@
defmodule MirageWeb.ModalComponent do
use MirageWeb, :live_component
@impl true
def render(assigns) do
~L"""
<div id="<%= @id %>" class="phx-modal"
phx-capture-click="close"
phx-window-keydown="close"
phx-key="escape"
phx-target="#<%= @id %>"
phx-page-loading>
<div class="phx-modal-content">
<%= live_patch raw("&times;"), to: @return_to, class: "phx-modal-close" %>
<%= live_component @socket, @component, @opts %>
</div>
</div>
"""
end
@impl true
def handle_event("close", _, socket) do
{:noreply, push_patch(socket, to: socket.assigns.return_to)}

12
lib/mirage_web/live/modal_component.html.leex

@ -0,0 +1,12 @@
<div id="<%= @id %>" class="phx-modal"
phx-capture-click="close"
phx-window-keydown="close"
phx-key="escape"
phx-target="#<%= @id %>"
phx-page-loading>
<div class="phx-modal-content">
<%= live_patch raw("&times;"), to: @return_to, class: "phx-modal-close" %>
<%= live_component @socket, @component, @opts %>
</div>
</div>

64
lib/mirage_web/live/note_live/form_component.ex

@ -0,0 +1,64 @@
defmodule MirageWeb.NoteLive.FormComponent do
use MirageWeb, :live_component
alias Mirage.Notes
@impl true
def update(%{note: note} = assigns, socket) do
changeset = Notes.change_note(note)
{:ok,
socket
|> assign(assigns)
|> assign(:changeset, changeset)}
end
@impl true
def handle_event("validate", %{"note" => note_params}, socket) do
changeset =
socket.assigns.note
|> Notes.change_note(note_params)
|> Map.put(:action, :validate)
{:noreply, assign(socket, :changeset, changeset)}
end
def handle_event("save", %{"note" => note_params}, socket) do
save_note(socket, socket.assigns.action, note_params)
end
defp save_note(socket, :edit, note_params) do
case Notes.update_note(socket.assigns.note, note_params) do
{:ok, note} ->
note
|> Notes.Tags.update_tags(note_params["topic_string"])
|> Notes.link_note()
{:noreply,
socket
|> put_flash(:info, "Note note updated successfully")
|> push_redirect(to: socket.assigns.return_to)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, :changeset, changeset)}
end
end
defp save_note(socket, :new, note_params) do
case Notes.create_note(note_params) do
{:ok, note} ->
note
|> Notes.preload_note()
|> Notes.Tags.update_tags(note_params["topic_string"])
|> Notes.link_note()
{:noreply,
socket
|> put_flash(:info, "Note note created successfully")
|> push_redirect(to: socket.assigns.return_to)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, changeset: changeset)}
end
end
end

20
lib/mirage_web/templates/note/form.html.eex → lib/mirage_web/live/note_live/form_component.html.leex

@ -1,16 +1,18 @@
<%= form_for @changeset, @action, fn f -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<h2><%= @title %></h2>
<%= f = form_for @changeset, "#",
id: "note-form",
phx_target: @myself,
phx_change: "validate",
phx_submit: "save",
phx_hook: "NoteForm" %>
<fieldset>
<%= text_input f, :title, placeholder: "What is this note about?", class: "title" %>
</fieldset>
<%= hidden_input f, :content, class: "editor-content" %>
<div id="editor"></div>
<div id="editor" phx-update="ignore"></div>
<fieldset>
<%= text_input f, :topic_string, placeholder: "Tags, comma-separated" %>
@ -18,6 +20,6 @@
</fieldset>
<div>
<%= submit "Save" %>
<%= submit "Save", phx_disable_with: "Saving..." %>
</div>
<% end %>
</form>

41
lib/mirage_web/live/note_live/index.ex

@ -0,0 +1,41 @@
defmodule MirageWeb.NoteLive.Index do
use MirageWeb, :live_view
alias Mirage.Notes
alias Mirage.Notes.Note
@impl true
def mount(_params, %{"user_id" => user_id}, socket) do
{:ok,
socket
|> assign(:notes, Notes.list_notes())
|> assign(:current_user, Mirage.Accounts.get_user!(user_id))}
end
@impl true
def handle_params(params, _url, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end
defp apply_action(socket, :edit, %{"id" => id}) do
socket
|> assign(:page_title, "Edit Note")
|> assign(:note, Notes.get_note!(id))
end
defp apply_action(socket, :new, _params) do
socket
|> assign(:page_title, "New Note")
|> assign(:note, %Note{})
end
defp apply_action(socket, :index, _params) do
socket
|> assign(:page_title, "Listing Notes")
|> assign(:note, nil)
end
defp list_notes do
Notes.list_notes()
end
end

50
lib/mirage_web/live/note_live/index.html.leex

@ -0,0 +1,50 @@
<%= if @current_user do %>
<%= live_render @socket, MirageWeb.GotoAnythingLive, id: "goto-anything-wrapper" %>
<% end %>
<header class="hero">
<h1>Listing Notes</h1>
<%= if @current_user do %>
<span><%= live_patch "New Note", to: Routes.note_index_path(@socket, :new), class: "button" %></span>
<% end %>
</header>
<%= if @live_action in [:new, :edit] do %>
<%= live_modal @socket, MirageWeb.NoteLive.FormComponent,
id: @note.id || :new,
title: @page_title,
action: @live_action,
note: @note,
return_to: Routes.note_index_path(@socket, :index) %>
<% end %>
<section class="notes">
<%= for note <- @notes do %>
<article class="note" id="note-<%= note.id %>">
<%= live_patch to: Routes.note_show_path(@socket, :show, note) do %>
<header class="width-full">
<h2 class="title">
<%= note.title %>
</h2>
</header>
<% end %>
<section>
<div class="content html width-full bg-content">
<%= raw note.content_html %>
</div>
</section>
<footer class="width-full bg-content">
<time datetime="<%= note.inserted_at %>"><%= Timex.from_now(note.inserted_at) %></time>
<span class="tags">
<%= for topic <- note.topics do %>
<span class="tag"><%= topic.text %></span>
<% end %>
</span>
</footer>
</article>
<% end %>
</section>

71
lib/mirage_web/live/note_live/show.ex

@ -0,0 +1,71 @@
defmodule MirageWeb.NoteLive.Show do
use MirageWeb, :live_view
alias Mirage.Notes
alias Mirage.Links
alias Mirage.Links.Link
@impl true
def mount(_params, %{"user_id" => user_id}, socket) do
{:ok, socket |> assign(:current_user, Mirage.Accounts.get_user!(user_id))}
end
@impl true
def handle_params(%{"id" => id}, _, socket) do
{:noreply,
socket
|> assign(:page_title, page_title(socket.assigns.live_action))
|> assign(
:note,
Notes.get_note!(id)
|> Notes.preload_note()
)
|> assign(:link_changeset, Link.changeset(%Link{}, %{note_id: id}))}
end
@impl true
def handle_event("delete", %{"id" => id}, socket) do
link = Notes.get_note!(id)
{:ok, _} = Notes.delete_note(link)
{:noreply, push_redirect(socket, to: Routes.note_index_path(socket, :index))}
end
defp page_title(:show), do: "Show Note"
defp page_title(:edit), do: "Edit Note"
def handle_event("delete_link", %{"link-id" => link_id}, socket) do
link = Links.get_link!(link_id)
{:ok, _} = Links.delete_link(link)
note =
socket.assigns.note.id
|> Notes.get_note!()
|> Notes.preload_note()
{:noreply, socket |> assign(%{note: note})}
end
@impl true
def handle_event("save_link", %{"link" => link}, socket) do
note_id = socket.assigns.note.id
with {:ok, link} <- Links.create_link(link),
{:ok, _note_link} <- Notes.create_note_link(%{link_id: link.id, note_id: note_id}) do
note =
note_id
|> Notes.get_note!()
|> Notes.preload_note()
{:noreply,
socket
|> assign(%{
note: note,
link_changeset: Link.changeset(%Link{}, %{note_id: note_id})
})}
else
_ ->
{:noreply, socket}
end
end
end

75
lib/mirage_web/live/note_live/show.html.leex

@ -0,0 +1,75 @@
<%= if @current_user do %>
<%= live_render @socket, MirageWeb.GotoAnythingLive, id: "goto-anything-wrapper" %>
<% end %>
<%= if @live_action in [:edit] do %>
<%= live_modal @socket, MirageWeb.NoteLive.FormComponent,
id: @note.id,
title: @page_title,
action: @live_action,
note: @note,
return_to: Routes.note_show_path(@socket, :show, @note) %>
<% end %>
<header class="hero">
<h1><span class="id"><%= "##{@note.id}" %></span> <%= @note.title %></h1>
<p><time datetime="<%= @note.inserted_at %>"><%= Timex.from_now(@note.inserted_at) %></time> / <time datetime="<%= @note.updated_at %>"><%= Timex.from_now(@note.updated_at) %></time></p>
<p class="tags">
<%= for topic <- @note.topics do %>
<span class="tag"><%= topic.text %></span>
<% end %>
</p>
</header>
<div class="width-full bg-content" phx-update="ignore" id="note-<%= @note.id %>">
<div class="content html">
<%= raw @note.content_html %>
</div>
</div>
<%= if not Enum.empty?(@note.backlinks) do %>
<section class="backlinks">
<h4>Backlinks</h4>
<%= for backlink <- @note.backlinks do %>
<div class="link-wrapper">
<%= live_patch backlink.title, to: Routes.note_show_path(@socket, :show, backlink), class: "link" %>
</div>
<% end %>
</section>
<% end %>
<%= if not Enum.empty?(@note.links) do %>
<section class="sources">
<h4>Sources</h4>
<%= for link <- @note.links do %>
<div class="link-wrapper">
<%= link to: link.url, class: "link" do %>
<span class="flex"><%= link.title || link.url %></span>
<span class="flex"><%= link.domain %></span>
<% end %>
<%= if @current_user do %>
<%= link "🗑️ DELETE", to: "#", phx_click: "delete_link", phx_value_link_id: link.id, data: [confirm: "Are you sure?"] %>
<% end %>
</div>
<% end %>
</section>
<% end %>
<%= if @current_user do %>
<%= f = form_for @link_changeset, "#", [phx_submit: :save_link] %>
<fieldset>
<%= hidden_input f, :note_id %>
<%= url_input f, :url, placeholder: "Add new link and press ENTER", autocomplete: "url" %>
</fieldset>
</form>
<% end %>
<%= if @current_user do %>
<div class="buttons">
<%= live_patch "Edit", to: Routes.note_show_path(@socket, :edit, @note), class: "button", class: "button" %>
<%= live_redirect "Back", to: Routes.note_index_path(@socket, :index), class: "button" %>
<%= link "Delete", to: "#", phx_click: "delete", phx_value_id: @note.id, data: [confirm: "Are you sure?"], class: "button" %>
</div>
<% end %>

8
lib/mirage_web/router.ex

@ -28,7 +28,6 @@ defmodule MirageWeb.Router do
live "/", HomeLive, :index
resources "/notes", NoteController, except: [:show]
resources "/topics", TopicController
resources "/settings", SettingController, only: [:index, :show, :edit, :update]
@ -39,7 +38,12 @@ defmodule MirageWeb.Router do
live "/links/:id", LinkLive.Show, :show
live "/links/:id/show/edit", LinkLive.Show, :edit
live "/notes/:id", ShowNoteLive
live "/notes", NoteLive.Index, :index
live "/notes/new", NoteLive.Index, :new
live "/notes/:id/edit", NoteLive.Index, :edit
live "/notes/:id", NoteLive.Show, :show
live "/notes/:id/show/edit", NoteLive.Show, :edit
end
scope "/user", MirageWeb do

26
lib/mirage_web/templates/note/_note.html.eex

@ -1,26 +0,0 @@
<article class="note">
<%= link to: Routes.live_path(@conn, MirageWeb.ShowNoteLive, @note) do %>
<header class="width-full">
<h2 class="title">
<%= @note.title %>
</h2>
</header>
<% end %>
<section>
<div class="content html width-full bg-content">
<%= raw @note.content_html %>
</div>
</section>
<footer class="width-full bg-content">
<time datetime="<%= @note.inserted_at %>"><%= Timex.from_now(@note.inserted_at) %></time>
<span class="tags">
<%= for topic <- @note.topics do %>
<span class="tag"><%= topic.text %></span>
<% end %>
</span>
</footer>
</article>

7
lib/mirage_web/templates/note/edit.html.eex

@ -1,7 +0,0 @@
<header class="hero">
<h1>Edit Note</h1>
</header>
<div class="width-full bg-content">
<%= render "form.html", Map.put(assigns, :action, Routes.note_path(@conn, :update, @note)) %>
</div>

15
lib/mirage_web/templates/note/index.html.eex

@ -1,15 +0,0 @@
<header class="hero">
<h1>Listing Notes</h1>
</header>
<section class="notes">
<%= for note <- @notes do %>
<%= render "_note.html", assigns |> Map.put(:note, note) %>
<% end %>
</section>
<%= if @current_user do %>
<div class="buttons">
<span><%= link "New Note", to: Routes.note_path(@conn, :new), class: "button" %></span>
</div>
<% end %>

5
lib/mirage_web/templates/note/new.html.eex

@ -1,5 +0,0 @@
<header class="hero">
<h1>New Note</h1>
</header>
<%= render "form.html", Map.put(assigns, :action, Routes.note_path(@conn, :create)) %>

28
lib/mirage_web/templates/topic/show.html.eex

@ -4,7 +4,33 @@
<section>
<%= for note <- @topic.notes do %>
<%= render MirageWeb.NoteView, "_note.html", assigns |> Map.put(:note, note) %>
<article class="note" id="note-<%= note.id %>">
<%= live_patch to: Routes.note_show_path(MirageWeb.Endpoint, :show, note) do %>
<header class="width-full">
<h2 class="title">
<%= note.title %>
</h2>
</header>
<% end %>
<!--
<section>
<div class="content html width-full bg-content">
<%= raw note.content_html %>
</div>
</section>
<footer class="width-full bg-content">
<time datetime="<%= note.inserted_at %>"><%= Timex.from_now(note.inserted_at) %></time>
<span class="tags">
<%= for topic <- note.topics do %>
<span class="tag"><%= topic.text %></span>
<% end %>
</span>
</footer>
-->
</article>
<% end %>
</section>

3
lib/mirage_web/views/note_view.ex

@ -1,3 +0,0 @@
defmodule MirageWeb.NoteView do
use MirageWeb, :view
end

2
mix.exs

@ -1,7 +1,7 @@
defmodule Mirage.MixProject do
use Mix.Project
@version "0.47.0"
@version "0.48.0"
def project do
[

Loading…
Cancel
Save