Frontend UI improvements

This commit is contained in:
Shadowfacts 2020-04-25 12:30:47 -04:00
parent 77bca46197
commit eba9e4be96
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
17 changed files with 643 additions and 43 deletions

View File

@ -1,3 +0,0 @@
/* This file is for your main application css. */
@import "./phoenix.css";

5
assets/css/app.scss Normal file
View File

@ -0,0 +1,5 @@
/* This file is for your main application css. */
/* @import "./phoenix.css"; */
@import "./normalize.css";
@import "./clacks.scss";

139
assets/css/clacks.scss Normal file
View File

@ -0,0 +1,139 @@
$sans-serif: -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;
$serif: "Times New Roman", serif;
$tint-color: #4b43e0;
// $link-color: blue;
$link-color: $tint-color;
$hr-color: darkgray;
body {
font-family: $sans-serif;
// always show scrollbar so effective page width doesn't change
overflow-y: scroll;
}
.container {
max-width: 720px;
margin: 0 auto;
}
a,
button.btn-link {
background: none;
border: none;
color: $link-color;
text-decoration: none;
&:hover {
cursor: pointer;
text-decoration: underline;
}
}
button[type=submit]:not(.btn-link) {
font-family: $sans-serif;
padding: 0.25rem 2rem;
background-color: $tint-color;
border: 1px solid darken($tint-color, 20%);
color: white;
&:hover {
cursor: pointer;
background-color: darken($tint-color, 10%);
}
}
hr {
border: 1px solid $hr-color;
margin: 0.75rem 0;
}
input:focus, textarea:focus {
outline: none;
border: 2px solid $tint-color;
}
h1, h2, h3 {
margin: 0.5rem 0;
}
header {
nav {
display: flex;
flex-direction: row;
justify-content: space-between;
ul {
display: flex;
padding: 0;
li {
display: inline;
list-style: none;
}
}
}
}
ul.status-list {
padding: 0;
margin: 0;
li {
list-style: none;
margin-bottom: 1rem;
}
}
.status {
padding: 0.5rem;
border: 1px solid #ddd;
background-color: #f2f2f2;
.status-meta {
display: flex;
flex-direction: row;
align-items: flex-start;
.status-author-nickname,
.status-author-username,
.status-meta-right {
display: inline;
margin: 0;
margin-right: 1rem;
font-size: 1rem;
}
.status-author-username {
font-weight: normal;
}
.status-meta-right {
flex-grow: 1;
text-align: right;
}
}
.status-content {
font-family: $serif;
font-size: 1.2rem;
margin: 1rem 0;
a {
text-decoration: underline;
}
}
}
.compose-status {
textarea {
display: block;
min-width: 100%;
max-width: 100%;
min-height: 4rem;
margin-bottom: 0.75rem;
padding: 0.5rem;
// we want 100% width to include the border
box-sizing: border-box;
}
}

350
assets/css/normalize.css vendored Normal file
View File

@ -0,0 +1,350 @@
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
}
/**
* Render the `main` element consistently in IE.
*/
main {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input { /* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select { /* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}

View File

@ -1,7 +1,7 @@
// We need to import the CSS so that webpack will load it. // We need to import the CSS so that webpack will load it.
// The MiniCssExtractPlugin is used to separate it out into // The MiniCssExtractPlugin is used to separate it out into
// its own CSS file. // its own CSS file.
import css from "../css/app.css" import css from "../css/app.scss"
// webpack automatically bundles all modules in your // webpack automatically bundles all modules in your
// entry points. Those entry points can be configured // entry points. Those entry points can be configured

View File

@ -2779,6 +2779,17 @@
"integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=",
"dev": true "dev": true
}, },
"clone-deep": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
"integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
"dev": true,
"requires": {
"is-plain-object": "^2.0.4",
"kind-of": "^6.0.2",
"shallow-clone": "^3.0.0"
}
},
"clone-response": { "clone-response": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
@ -9332,6 +9343,36 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true "dev": true
}, },
"sass": {
"version": "1.26.3",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.26.3.tgz",
"integrity": "sha512-5NMHI1+YFYw4sN3yfKjpLuV9B5l7MqQ6FlkTcC4FT+oHbBRUZoSjHrrt/mE0nFXJyY2kQtU9ou9HxvFVjLFuuw==",
"dev": true,
"requires": {
"chokidar": ">=2.0.0 <4.0.0"
}
},
"sass-loader": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.3.1.tgz",
"integrity": "sha512-tuU7+zm0pTCynKYHpdqaPpe+MMTQ76I9TPZ7i4/5dZsigE350shQWe5EZNl5dBidM49TPET75tNqRbcsUZWeNA==",
"dev": true,
"requires": {
"clone-deep": "^4.0.1",
"loader-utils": "^1.0.1",
"neo-async": "^2.5.0",
"pify": "^4.0.1",
"semver": "^6.3.0"
},
"dependencies": {
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
}
}
},
"sax": { "sax": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
@ -9412,6 +9453,15 @@
"safe-buffer": "^5.0.1" "safe-buffer": "^5.0.1"
} }
}, },
"shallow-clone": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
"integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
"dev": true,
"requires": {
"kind-of": "^6.0.2"
}
},
"shebang-command": { "shebang-command": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",

View File

@ -19,6 +19,8 @@
"optimize-css-assets-webpack-plugin": "^4.0.0", "optimize-css-assets-webpack-plugin": "^4.0.0",
"uglifyjs-webpack-plugin": "^1.2.4", "uglifyjs-webpack-plugin": "^1.2.4",
"webpack": "4.4.0", "webpack": "4.4.0",
"webpack-cli": "^2.0.10" "webpack-cli": "^2.0.10",
"sass": "^1.26.3",
"sass-loader": "^7.3.1"
} }
} }

View File

@ -29,8 +29,8 @@ module.exports = (env, options) => ({
} }
}, },
{ {
test: /\.css$/, test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'] use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
} }
] ]
}, },

View File

@ -55,7 +55,7 @@ defmodule ClacksWeb.FrontendController do
"type" => "Create", "type" => "Create",
"object" => %{"type" => "Note", "attributedTo" => author_id} = note "object" => %{"type" => "Note", "attributedTo" => author_id} = note
} = data } = data
} <- Activity.get(id), } = activity <- Activity.get(id),
%Actor{} = author <- Actor.get_by_ap_id(author_id) do %Actor{} = author <- Actor.get_by_ap_id(author_id) do
case conn.assigns[:format] do case conn.assigns[:format] do
"activity+json" -> "activity+json" ->
@ -64,7 +64,7 @@ defmodule ClacksWeb.FrontendController do
"html" -> "html" ->
render(conn, "status.html", %{ render(conn, "status.html", %{
current_user: current_user, current_user: current_user,
note: note, status: activity,
author: author author: author
}) })
end end
@ -99,13 +99,13 @@ defmodule ClacksWeb.FrontendController do
with %Activity{ with %Activity{
data: %{ data: %{
"type" => "Create", "type" => "Create",
"object" => %{"type" => "Note", "attributedTo" => author_id} = note "object" => %{"type" => "Note", "attributedTo" => author_id}
} }
} <- Activity.get(id), } = activity <- Activity.get(id),
%Actor{} = author <- Actor.get_by_ap_id(author_id) do %Actor{} = author <- Actor.get_by_ap_id(author_id) do
render(conn, "reply.html", %{ render(conn, "status.html", %{
current_user: current_user, current_user: current_user,
note: note, status: activity,
author: author author: author
}) })
else else

View File

@ -58,6 +58,8 @@ defmodule ClacksWeb.Router do
pipe_through :browser_authenticated pipe_through :browser_authenticated
post "/post", FrontendController, :post_status post "/post", FrontendController, :post_status
get "/status/:id/reply", FrontendController, :reply
end end
scope "/", ClacksWeb do scope "/", ClacksWeb do

View File

@ -1,16 +1,24 @@
<div class="status"> <div class="status">
<h2> <div class="status-meta">
<a href="<%= @author.ap_id %>"> <h2 class="status-author-nickname">
<%= @author.data["preferredUsername"] %> <a href="<%= @author.ap_id %>">
</a> <%= @author.data["preferredUsername"] %>
</h2> </a>
<h3> </h2>
<a href="<%= @author.ap_id %>"> <h3 class="status-author-username">
<%= @author.data["name"] %> <a href="<%= @author.ap_id %>">
</a> <%= display_username(@author) %>
</h3> </a>
<a href="<%= @note["url"] %>">Permalink</a> </h3>
<p class="status-meta-right">
<span><%= display_timestamp(@note["published"]) %></span>
<a href="<%= @note["url"] %>" class="status-permalink">Permalink</a>
</p>
</div>
<div class="status-content"> <div class="status-content">
<%= @note["content"] %> <%= @note["content"] %>
</div> </div>
<div class="status-actions">
<a href="<%= Routes.frontend_path(@conn, :reply, @status.id) %>">Reply</a>
</div>
</div> </div>

View File

@ -1,7 +1,7 @@
<ul> <ul class="status-list">
<%= for {status, author} <- @statuses_with_authors do %> <%= for {status, author} <- @statuses_with_authors do %>
<li> <li>
<%= render "_status.html", author: author, note: status.data["object"] %> <%= render "_status.html", conn: @conn, author: author, status: status, note: status.data["object"] %>
</li> </li>
<% end %> <% end %>
</ul> </ul>

View File

@ -1,11 +1,9 @@
<h1>Home</h1> <h1>Home</h1>
<p>
Logged in as <a href="<%= Routes.actor_path(@conn, :get, @user.username) %>"><%= @user.username %></a>
</p>
<%= form_tag Routes.frontend_path(@conn, :post_status), method: :post do %> <%= form_tag Routes.frontend_path(@conn, :post_status), method: :post, class: "compose-status" do %>
<textarea id="content" name="content" cols="30" rows="10"></textarea> <textarea id="content" name="content" rows="5" placeholder="What's up?" required></textarea>
<%= submit "Post" %> <%= submit "Post" %>
<hr>
<% end %> <% end %>
<%= render "_timeline.html", statuses_with_authors: @statuses_with_authors %> <%= render "_timeline.html", conn: @conn, statuses_with_authors: @statuses_with_authors %>

View File

@ -2,4 +2,4 @@
<h2>@<%= @actor.data["name"] %></h2> <h2>@<%= @actor.data["name"] %></h2>
<p><%= @actor.data["summary"] %></p> <p><%= @actor.data["summary"] %></p>
<%= render "_timeline.html", statuses_with_authors: Enum.map(@statuses, &({&1, @actor})) %> <%= render "_timeline.html", conn: @conn, statuses_with_authors: Enum.map(@statuses, &({&1, @actor})) %>

View File

@ -1,9 +1,10 @@
<%= render "_status.html", author: @author, note: @note %> <%= render "_status.html", conn: @conn, author: @author, status: @status, note: @status.data["object"] %>
<hr> <hr>
<%= form_tag Routes.frontend_path(@conn, :post_status), method: :post do %> <%= form_tag Routes.frontend_path(@conn, :post_status), method: :post, class: "compose-status" do %>
<input type="hidden" name="in_reply_to" value="<%= @note["id"] %>"> <input type="hidden" name="in_reply_to" value="<%= @status.data["object"]["id"] %>">
<textarea id="content" name="content" cols="30" rows="10" placeholder="Reply"></textarea> <textarea id="content" name="content" rows="5" placeholder="Reply" required></textarea>
<%= submit "Post" %> <%= submit "Post" %>
<hr>
<% end %> <% end %>

View File

@ -4,7 +4,7 @@
<meta charset="utf-8"/> <meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/> <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title><%= assigns[:page_title] || "Clacks" %></title> <title><%= assigns[:page_title] || instance_name() %></title>
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/> <link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
</head> </head>
<body> <body>
@ -15,15 +15,20 @@
<li><a href="/"><%= instance_name() %></a></li> <li><a href="/"><%= instance_name() %></a></li>
</ul> </ul>
<ul> <ul>
<li> <%= if @conn.assigns[:user] do %>
<%= if @conn.assigns[:user] do %> <li>
Logged in as <a href="<%= Routes.actor_path(@conn, :get, @conn.assigns[:user].username) %>"><%= @conn.assigns[:user].username %></a>.
</li>
<li>
<%= form_for @conn, Routes.login_path(@conn, :logout), [method: :post], fn f -> %> <%= form_for @conn, Routes.login_path(@conn, :logout), [method: :post], fn f -> %>
<%= submit "Log Out", class: "btn-link" %> <%= submit "Log Out", class: "btn-link" %>
<% end %> <% end %>
<% else %> </li>
<% else %>
<li>
<a href="<%= login_path(@conn) %>">Log In</a> <a href="<%= login_path(@conn) %>">Log In</a>
<% end %> </li>
</li> <% end %>
</ul> </ul>
</nav> </nav>
</section> </section>

View File

@ -1,3 +1,46 @@
defmodule ClacksWeb.FrontendView do defmodule ClacksWeb.FrontendView do
use ClacksWeb, :view use ClacksWeb, :view
alias Clacks.Actor
@spec display_username(actor :: Actor.t()) :: String.t()
def display_username(%Actor{local: true, data: %{"name" => name}}) do
"@" <> name
end
def display_username(%Actor{local: false, ap_id: ap_id, data: %{"name" => name}}) do
%URI{host: host} = URI.parse(ap_id)
"@" <> name <> "@" <> host
end
@absolute_timestamp_threshold 24 * 60 * 60
def display_timestamp(str) when is_binary(str) do
display_timestamp(Timex.parse!(str, "{ISO:Extended}"))
end
def display_timestamp(datetime) do
diff = Timex.diff(Timex.now(), datetime, :seconds)
cond do
diff < 60 ->
# less than a minute, seconds
"#{diff}sec"
diff < 60 * 60 ->
# less than an hour, minutes
"#{Integer.floor_div(diff, 60)}min"
diff < 60 * 60 * 24 ->
# less than a day, hours
"#{Integer.floor_div(diff, 60 * 60)}hr"
diff < 60 * 60 * 24 * 7 ->
# less than a week, days
"#{Integer.floor_div(diff, 60 * 60 * 24)}d"
true ->
Timex.format!(datetime, "%FT%T%:z", :strftime)
end
end
end end