Browse Source

feat(guestbook): Allow loigc and routing.

jackyalcine 8 months ago
parent
commit
3f14f14311
Signed by: Jacky Alciné <yo@jacky.wtf> GPG Key ID: 537A4F904B15268D

+ 3
- 4
.postcssrc.js View File

@@ -8,11 +8,11 @@ module.exports = {
8 8
     'postcss-reporter': {
9 9
       clearMessages: false
10 10
     },
11
-    'postcss-import': {},
11
+    autoprefixer: {},
12 12
     'postcss-url': [
13
-      { filter: '**/*wf', url: 'inline' }
13
+      {filter: '**/*.woff', url: 'copy', assetsPath: 'fonts', useHash: true},
14
+      {filter: '**/*.woff2', url: 'copy', assetsPath: 'fonts', useHash: true},
14 15
     ],
15
-    autoprefixer: {},
16 16
     'postcss-strip-inline-comments': {},
17 17
     'postcss-ordered-values': {},
18 18
     'postcss-reduce-initial': {},
@@ -21,7 +21,6 @@ module.exports = {
21 21
     'postcss-font-variant': {},
22 22
     'postcss-font-display': {},
23 23
     'postcss-font-smoothing': {},
24
-    'postcss-inline': {},
25 24
     'postcss-pxtorem': {
26 25
       unitPrecision: 8,
27 26
       propWhiteList: [],

+ 7
- 6
.sassrc.js View File

@@ -7,13 +7,12 @@ var buildDir = path.join(CWD, "priv", "static", "assets");
7 7
 
8 8
 const options = {
9 9
   "includePaths": [
10
-    path.resolve(assetsDir),
11
-    path.resolve(assetsDir, 'css'),
12
-    path.resolve(nodeModulesDir, 'leaflet', 'dist'),
13
-    path.resolve(nodeModulesDir, 'typeface-source-code'),
14
-    path.resolve(nodeModulesDir, 'typeface-open-sans'),
15
-    path.resolve(nodeModulesDir, 'typeface-crimson-text'),
10
+    assetsDir,
16 11
     nodeModulesDir,
12
+    path.join(nodeModulesDir, 'typeface-open-sans/'),
13
+    path.join(nodeModulesDir, 'typeface-crimson-text/'),
14
+    path.join(nodeModulesDir, 'typeface-source-code/'),
15
+    path.join(nodeModulesDir, 'leaflet', 'dist/'),
17 16
   ],
18 17
   "relativeUrls": false,
19 18
   "engines": {
@@ -21,6 +20,8 @@ const options = {
21 20
   }
22 21
 }
23 22
 
23
+console.log(options.includePaths);
24
+
24 25
 const eyeglass = new Eyeglass({
25 26
   eyeglass: {
26 27
     assets: {

+ 2
- 2
config/dev.exs View File

@@ -12,9 +12,9 @@ config :koype, Koype.Web.Endpoint,
12 12
     :phoenix_swagger
13 13
   ],
14 14
   live_reload: [
15
-    interval: 50,
15
+    interval: 1000,
16 16
     patterns: [
17
-      # ~r{priv/static/assets/.*(js|css|png|jpeg|jpg|gif|svg|json)$},
17
+      ~r{priv/static/assets/.*(js|css|png|jpeg|jpg|gif|svg|json)$},
18 18
       ~r{priv/themes/.*(js|css|png|jpeg|jpg|gif|svg|json)$},
19 19
       ~r{priv/gettext/.*(po)$},
20 20
       ~r{web/views/.*(ex)$},

+ 5
- 5
lib/cache.ex View File

@@ -27,7 +27,7 @@ defmodule Koype.Cache do
27 27
 
28 28
     case Cachex.get(:koype, key) do
29 29
       {:ok, value} ->
30
-        Logger.info("Found '#{inspect(value)}' for '#{key}' in cache.")
30
+        Logger.debug("Found '#{inspect(value)}' for '#{key}' in cache.")
31 31
         {:ok, value}
32 32
 
33 33
       {:error, error} ->
@@ -76,10 +76,10 @@ defmodule Koype.Cache do
76 76
   def delete(key) do
77 77
     case Cachex.del(:koype, key) do
78 78
       {:error, error} ->
79
-        Logger.error("Ran into an error when deleting #{key}: #{inspect(error)}")
79
+        Logger.warn("Ran into an error when deleting #{key}: #{inspect(error)}")
80 80
 
81 81
       {:ok, _} ->
82
-        Logger.warn("Removed #{key} from cache.")
82
+        Logger.debug("Removed #{key} from cache.")
83 83
     end
84 84
 
85 85
     :ok
@@ -97,7 +97,7 @@ defmodule Koype.Cache do
97 97
 
98 98
     case result do
99 99
       {:ok, value} ->
100
-        Logger.info("Refreshed #{key} in cache.")
100
+        Logger.debug("Refreshed #{key} in cache.")
101 101
         :ok = set(key, value)
102 102
         {:ok, value}
103 103
 
@@ -116,7 +116,7 @@ defmodule Koype.Cache do
116 116
         {:error, error}
117 117
 
118 118
       {:ok, nil} ->
119
-        Logger.info("Value for #{key} didn't exist; pruning.", source: :cache)
119
+        Logger.debug("Value for #{key} didn't exist; pruning.", source: :cache)
120 120
         :ok = delete(key)
121 121
         {:error, :not_found_in_cache}
122 122
 

+ 3
- 3
lib/repo/webmention.ex View File

@@ -59,9 +59,9 @@ defmodule Koype.Repo.Webmention do
59 59
 
60 60
     defp do_extract_source_mf2(mf2) do
61 61
       %{
62
-        "published" => IndieWeb.MF2.get_value!(mf2, "published", [nil]) |> List.first(),
63
-        "title" => IndieWeb.MF2.get_value!(mf2, "name", [nil]) |> List.first(),
64
-        "content" => IndieWeb.MF2.get_value!(mf2, "content", [nil]) |> List.first()
62
+        "published" => mf2 |> IndieWeb.MF2.get_value!("published", [nil]) |> List.first(),
63
+        "title" => mf2 |> IndieWeb.MF2.get_value!("name", [nil]) |> List.first(),
64
+        "content" => mf2 |> IndieWeb.MF2.get_value!("content", [nil]) |> List.first()
65 65
       }
66 66
     end
67 67
   end

+ 14
- 6
lib/template/render.ex View File

@@ -20,6 +20,8 @@ defmodule Koype.Template.Renderer do
20 20
   require Logger
21 21
 
22 22
   def render_partial(definition_name, file_path, template_data) do
23
+    Logger.info("Attempting to render partial template '#{file_path}'..")
24
+
23 25
     {time, value} =
24 26
       :timer.tc(fn ->
25 27
         try do
@@ -34,8 +36,9 @@ defmodule Koype.Template.Renderer do
34 36
           end
35 37
         rescue
36 38
           value ->
37
-            Logger.debug("Underlying exception: #{inspect(value)}")
38
-            raise(Koype.Template.Error, file: file_path, raw: value)
39
+            Logger.debug(fn -> "Underlying exception: #{inspect(value)}" end)
40
+            Apex.ap([file_path, value])
41
+            reraise(Koype.Template.Error, file: file_path, raw: value)
39 42
         end
40 43
       end)
41 44
 
@@ -55,8 +58,11 @@ defmodule Koype.Template.Renderer do
55 58
           render_partial(definition_name, template_name <> ".html", do_decorate(template_data))
56 59
         end)
57 60
         |> Enum.reduce_while({:ok, ""}, fn
58
-          {:ok, template}, {:ok, acc} -> {:cont, {:ok, Enum.join([acc, template])}}
59
-          {:error, _} = error, _ -> {:halt, error}
61
+          {:ok, template}, {:ok, acc} ->
62
+            {:cont, {:ok, Enum.join([acc, template])}}
63
+
64
+          {:error, _} = error, _ ->
65
+            {:halt, error}
60 66
         end)
61 67
       end)
62 68
 
@@ -69,8 +75,10 @@ defmodule Koype.Template.Renderer do
69 75
     theme_root = Koype.Template.Definition.root_dir(definition)
70 76
     theme_file_path = Path.join(theme_root, file_path)
71 77
 
72
-    text = File.read!(theme_file_path)
73
-    Liquid.Template.parse(text)
78
+    case File.read(theme_file_path) do
79
+      {:ok, text} -> Liquid.Template.parse(text)
80
+      {:error, err} -> {:error, :fetch_template_failed, reason: :file.format_error(err)}
81
+    end
74 82
   end
75 83
 
76 84
   defp do_apply_data(type, data)

+ 4
- 2
lib/web.ex View File

@@ -49,12 +49,14 @@ defmodule Koype.Web do
49 49
         try do
50 50
           Koype.Template.render_page(current_template, page_name, params)
51 51
         rescue
52
-          Koype.Template.Error ->
53
-            Logger.warn("Ran into an error in the template.")
52
+          e in [Koype.Template.Error] ->
53
+            Logger.warn("Ran into an error in the template: #{inspect(e)}.")
54
+            Logger.error(__STACKTRACE__)
54 55
 
55 56
             {:error,
56 57
              conn
57 58
              |> put_resp_content_type("application/json")
59
+             |> put_new_view(Koype.Web.ErrorView)
58 60
              |> Explode.internal_server_error("Failed to render a template.")}
59 61
         end
60 62
       end

+ 8
- 1
lib/webmention.ex View File

@@ -209,9 +209,16 @@ defmodule Koype.Webmention do
209 209
     end
210 210
   end
211 211
 
212
-  def fetch_for(entry, type \\ :all) do
212
+  def fetch_for(entry, type \\ :all)
213
+
214
+  def fetch_for(%Koype.Repo.Entry{} = entry, type) do
213 215
     entry
214 216
     |> Koype.Repo.Entry.get_uri()
217
+    |> fetch_for(type)
218
+  end
219
+
220
+  def fetch_for(path, type) do
221
+    path
215 222
     |> Koype.Repo.Webmention.fetch(type)
216 223
     |> Enum.map(fn model ->
217 224
       with({:ok, json} <- Koype.Repo.Webmention.Json.find(model)) do

+ 7
- 3
priv/themes/default/.koype.json View File

@@ -4,16 +4,20 @@
4 4
   "uri": "https://themes.koype.net/view/default",
5 5
   "paths": {
6 6
     "/": {
7
-      "path": "home.html.liquid",
7
+      "path": "home",
8 8
       "title": "Home"
9 9
     },
10 10
     "/follow": {
11
-      "path": "follow.html.liquid",
11
+      "path": "follow",
12 12
       "title": "Follow"
13 13
     },
14 14
     "/stream": {
15
-      "path": "stream.html.liquid",
15
+      "path": "stream",
16 16
       "title": "Stream"
17
+    },
18
+    "/guestbook": {
19
+      "path": "guestbook/view",
20
+      "title": "Guestbook"
17 21
     }
18 22
   }
19 23
 }

+ 86
- 0
priv/themes/default/guestbook/view.html.liquid View File

@@ -0,0 +1,86 @@
1
+<article class="w-100 mw8 center h-feed v-mid flex flex-column flex-row-l flex-wrap">
2
+  <section class="w-100 w-70-l order-1">
3
+    <div class="pa2">
4
+      <h1 class="p-name lh-title tracked-tight f2 f1-l">
5
+        <a href="{% route guestbook.view %}" class="u-url u-uid color-inherit link">
6
+          {{ h.card.preferred_name }}'s Guestbook
7
+        </a>
8
+      </h1>
9
+      <p class="p-summary lh-copy f4 f3-l measure">
10
+        This page holds notes from people who I have in my
11
+        <a target="_blank"
12
+           class="blue"
13
+          href="http://indieweb.org/Koype#Address_Book">personal addressbook</a>.
14
+        It's not moderated (yet) and it only holds the last few
15
+        <a target="_blank" class="blue" href="https://indieweb.org/note">note</a>
16
+        responses people have sent to this page.
17
+      </p>
18
+      {% if face_count >= 1 %}
19
+        <ul class="list dib pa0 ma0 ml1">
20
+          {% for face in faces %}
21
+            <li style="margin-left: -1rem" class="di"><a class="link" href="{{ face.url }}"><img class="w-auto h3 br-100" src="{{ face.photo }}"></a></li>
22
+          {% endfor %}
23
+        </ul>
24
+      {% else %}
25
+        <p class="lh-copy measure-narrow black-60 f5">
26
+          No one's left me a note.
27
+        </p>
28
+      {% endif %}
29
+    </div>
30
+  </section>
31
+  <aside class="shadow-4 w-100 w-30-l order-2 mt4-l bg-lightest-blue black">
32
+    <div class="pa3 pt0 pb4">
33
+      <h3 class="tracked-tight f4 f3-l">Leaving A Note</h3>
34
+      <p class="f5 measure">
35
+        You can leave a note on this page by sending a
36
+        <a class="color-inherit navy" href="https://webmention.net/">webmention</a> to it.
37
+        {% if face_count == 0 %}
38
+          <br />
39
+          <mark>Be the first one!</mark>
40
+        {% endif %}
41
+      </p>
42
+      <h4 class="tracked-tight lh-subtitle f5 f4-l">Manual Note Submission</h4>
43
+      <p class="f5 measure">
44
+        Use the form below to manually send a webmention to this page.
45
+      </p>
46
+      <form method="post" action="{% route indie_webmention.incoming %}" class="w-100 dark-gray mt2 flex flex-row flex-wrap measure-l">
47
+        <input type=hidden name=target value="{% route current %}" />
48
+        <input type=hidden name=_format value="html" />
49
+        <label class="lh-copy gray f6 pb2 db w-100 tl" for="source">Enter your response URL below!</label>
50
+        <input name="source" placeholder="https://yoursite.here/response" type="url" class="mb2 ma0-ns code input-reset ba b--black-20 pa2 db w-100 w-75-ns center f6" id="manualWebmentionId">
51
+        <button class="pointer input-reset pa2 w-100 center mt1 mt0-ns w-25-ns v-mid ba b--black bg-near-black near-white">
52
+          <i class="v-mid w1 h1 gray" data-feather="zap"></i>
53
+          <span class="v-mid">Send</span>
54
+        </button>
55
+      </form>
56
+      <h4 class="tracked-tight lh-subtitle f5 f4-l">Site-less Note Submission</h4>
57
+      <p class="f5 measure mb4">
58
+        You can use <a class="color-inherit blue">Comment Parade</a> to leave a
59
+        comment as well.
60
+      </p>
61
+      <a class="pointer pa2 link grow w-100 center mt1 mt0-ns v-mid ba b--black bg-near-black near-white" href="https://quill.p3k.io/?dontask=1&me=https://commentpara.de&reply={% route current %}">
62
+        Leave Note Anonymously
63
+      </a>
64
+    </div>
65
+  </aside>
66
+  <main class="order-3 w-100 mt3">
67
+  {% if note_count != 0 %}
68
+    {% for note in notes %}
69
+      <section class="h-entry ma2 flex flex-row flex-wrap">
70
+        <a class="link mt1 w3" href="{{ note.author.url }}">
71
+          <img class="w-100 shadow-1 h3 br-100" src="{{ note.author.photo }}">
72
+        </a>
73
+        <div class="shadow-4 br3 bg-light-gray black-70 measure-wide w-100 mh2 pa3 tj lh-copy">
74
+          {{ note.content.html }}
75
+        </div>
76
+        <div class="w-100 measure-wide mt3 ml5 tr f5">
77
+          &mdash;
78
+          <a href="{{ note.author.url }}" class="color-inherit u-author h-card p-name">{{ note.author.name }} </a>;
79
+          posted
80
+          <a href="{{ note.url }}" class="color-inherit u-url"><time datetime="{{ note.published_at }}">just now</time></a>
81
+        </div>
82
+      </section>
83
+    {% endfor %}
84
+  {% endif %}
85
+  </main>
86
+</article>

+ 4
- 0
priv/themes/default/home.html.liquid View File

@@ -45,5 +45,9 @@
45 45
         {% render name=stream/_entry.html %}
46 46
       </div>
47 47
     {% endif %}
48
+    <h3 class="f5 f4-l mv1 lh-title black-70">Guestbook</h3>
49
+    <p class="lh-copy f5">
50
+      Check out the latest posts to my <a href="{% route guestbook.view %}">guestbook</a>.
51
+    </p>
48 52
   </section>
49 53
 </section>

+ 0
- 3
priv/watchers/npm-parcel View File

@@ -1,3 +0,0 @@
1
-#!/bin/env sh
2
-
3
-npm run parcel:watch

+ 37
- 0
web/controllers/guestbook_controller.ex View File

@@ -0,0 +1,37 @@
1
+# Koype: a IndieWeb-focused, single-tenant website engine for people.
2
+#
3
+# Copyright © 2019 Jacky Alciné <jacky.is@black.af>
4
+#
5
+# This file belongs to the Koype project.
6
+#
7
+# This program is free software: you can redistribute it and/or modify
8
+# it under the terms of the GNU Affero General Public License as published by
9
+# the Free Software Foundation, either version 3 of the License, or
10
+# (at your option) any later version.
11
+#
12
+# This program is distributed in the hope that it will be useful,
13
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
+# GNU Affero General Public License for more details.
16
+#
17
+# You should have received a copy of the GNU Affero General Public License
18
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
19
+defmodule Koype.Web.GuestbookController do
20
+  use Koype.Web, :controller
21
+
22
+  def view(conn, _params) do
23
+    notes =
24
+      ~w(reply note)
25
+      |> Enum.map(&Koype.Webmention.fetch_for(guestbook_path(conn, :view), &1))
26
+      |> List.flatten()
27
+      |> Enum.take(5)
28
+
29
+    faces = notes |> Enum.map(&Map.get(&1, :author)) |> Enum.uniq_by(&Map.get(&1, "url"))
30
+
31
+    render_template_page(
32
+      conn,
33
+      "guestbook/view",
34
+      %{"notes" => notes, "faces" => faces, "face_count" => length(faces), "note_count" => length(notes)}
35
+    )
36
+  end
37
+end

+ 2
- 0
web/router.ex View File

@@ -67,6 +67,8 @@ defmodule Koype.Web.Router do
67 67
     get("/subscribe", FeedController, :subscribe)
68 68
     get("/feeds/activitypub", FeedController, :activitypub)
69 69
     get("/feeds/:format", FeedController, :render)
70
+
71
+    get("/guestbook", GuestbookController, :view)
70 72
   end
71 73
 
72 74
   scope "/media/", Koype.Web do

web/static/fonts.css → web/static/fonts.scss View File


+ 13
- 7
web/static/koype.scss View File

@@ -17,10 +17,11 @@ body {
17 17
 
18 18
   @extend .near-black, .bg-white, .sans-serif;
19 19
 
20
-  &::selection {
21
-    background-color: $near-black;
22
-    color: $near-white;
23
-  }
20
+}
21
+
22
+*::selection {
23
+  background-color: $black-80;
24
+  color: $white-80;
24 25
 }
25 26
 
26 27
 article.h-entry,
@@ -228,13 +229,18 @@ form div.entry {
228 229
 }
229 230
 
230 231
 body > header[role="banner"] {
231
-  @extend .f6, .shadow-1, .w-100, .bt, .bw2;
232
+  @extend .f6, .w-100, .bt, .bw2;
232 233
 
233 234
   &.koype__logged-in {
234
-    @extend .bg-near-black, .near-white;
235
+    @extend .bg-near-black, .near-white, .shadow-1;
235 236
   }
236 237
 
237 238
   &.koype__logged-out {
238
-    @extend .bg-dark-gray, .near-black;
239
+    @extend .bg-transparent, .near-black, .o-20, .glow;
239 240
   }
240 241
 }
242
+
243
+mark {
244
+  background-color: $yellow;
245
+  color: $black-80;
246
+}

+ 40
- 0
web/views/guestbook_view.ex View File

@@ -0,0 +1,40 @@
1
+# Koype: a IndieWeb-focused, single-tenant website engine for people.
2
+# 
3
+# Copyright © 2019 Jacky Alciné <jacky.is@black.af>
4
+# 
5
+# This file belongs to the Koype project.
6
+# 
7
+# This program is free software: you can redistribute it and/or modify
8
+# it under the terms of the GNU Affero General Public License as published by
9
+# the Free Software Foundation, either version 3 of the License, or
10
+# (at your option) any later version.
11
+# 
12
+# This program is distributed in the hope that it will be useful,
13
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
+# GNU Affero General Public License for more details.
16
+# 
17
+# You should have received a copy of the GNU Affero General Public License
18
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
19
+defmodule Koype.Web.GuestbookView do
20
+  use Koype.Web, :view
21
+
22
+  def title("guestbook/view", %{"note_count" => count}) when count > 0, do: "(#{count}) Guestbook"
23
+  def title("guestbook/view", _), do: "Guestbook"
24
+
25
+  def tag({:link, rel}, "guestbook/view", assigns) do
26
+    rels = %{
27
+      canonical: guestbook_path(Koype.Web.Endpoint, :view),
28
+      webmention: Koype.host() <> indie_webmention_path(Koype.Web.Endpoint, :incoming)
29
+    }
30
+
31
+    [rel: rel, href: rels[rel]]
32
+  end
33
+
34
+  def tag(_, _, _), do: nil
35
+
36
+  def tags(tag, template, assigns)
37
+  def tags(:meta, _, _), do: ~w()a
38
+  def tags(:link, "guestbook/view", _assigns), do: ~w(canonical webmention)a
39
+  def tags(_, _, _), do: []
40
+end

+ 1
- 1
web/views/layout_view.ex View File

@@ -28,7 +28,7 @@ defmodule Koype.Web.LayoutView do
28 28
 
29 29
     Logger.info("Determined the page's view to be #{inspect(module)} for #{template}.")
30 30
 
31
-    module_title = module.title(template, assigns)
31
+    module_title = if function_exported?(module, :title, 2), do: module.title(template, assigns)
32 32
 
33 33
     if module_title, do: "#{module_title} - #{base_title()}", else: base_title()
34 34
   end

Loading…
Cancel
Save