Browse Source

feat(theming): Add more improvements for simplest.

jackyalcine 2 months ago
parent
commit
5ab569f223
Signed by: Jacky Alciné <yo@jacky.wtf> GPG Key ID: 537A4F904B15268D

+ 2
- 0
lib/setting.ex View File

@@ -2,6 +2,8 @@ defmodule Koype.Setting do
2 2
   @moduledoc false
3 3
   alias Koype.Repo.Setting, as: Model
4 4
 
5
+  @doc "Obtains the setting from Koype's repository."
6
+  @spec get(binary(), binary()) :: binary() | nil
5 7
   def get(name, value \\ nil) do
6 8
     case Koype.Cache.get("setting:#{name}") do
7 9
       {:ok, nil} ->

+ 7
- 3
lib/template/decorator.ex View File

@@ -32,10 +32,13 @@ defmodule Koype.Template.Decorator do
32 32
   end
33 33
 
34 34
   defp system(theme: theme) do
35
-    {:ok, theme_data} = Koype.Theme.config_for(theme)
36
-    %{"version" => Koype.version(), "theme" => theme_data}
35
+    case Koype.Theme.config_for(theme) do
36
+      {:ok, theme_data} -> %{"version" => Koype.version(), "theme" => theme_data}
37
+      nil -> %{"version" => Koype.version(), "theme" => %{"id" => theme}}
38
+    end
37 39
   end
38 40
 
41
+  # TODO: Build this dynamically based on the set of h-card values set.
39 42
   defp hcard() do
40 43
     %{
41 44
       "uri" => Koype.host(),
@@ -44,7 +47,8 @@ defmodule Koype.Template.Decorator do
44 47
       "note" => Koype.Profile.note(),
45 48
       "name" => Koype.Profile.name(),
46 49
       "nickname" => Koype.Profile.nickname(),
47
-      "timezone" => Koype.Profile.timezone()
50
+      "timezone" => Koype.Profile.timezone(),
51
+      "email" => Koype.Profile.email()
48 52
     }
49 53
   end
50 54
 

+ 12
- 2
lib/template/render.ex View File

@@ -62,10 +62,16 @@ defmodule Koype.Template.Renderer do
62 62
   end
63 63
 
64 64
   # TODO: Allow each individual page to define to render its layout.
65
-  def render_page(theme_name, file_path, template_data) do
65
+  @spec render_page(binary(), binary(), map()) :: {:ok, binary()} | {:error, any()}
66
+  def render_page(theme_name, file_path, template_data) when is_binary(theme_name) do
66 67
     Koype.Template.Tags.register()
67 68
     Koype.Template.Filters.register()
68 69
 
70
+    Logger.debug("Beginning render of page.",
71
+      theme_name: theme_name,
72
+      file_path: file_path
73
+    )
74
+
69 75
     {time, value} =
70 76
       :timer.tc(fn ->
71 77
         data = Koype.Template.Decorator.decorate(template_data, theme: theme_name)
@@ -91,7 +97,11 @@ defmodule Koype.Template.Renderer do
91 97
         end)
92 98
       end)
93 99
 
94
-    Logger.debug("Took #{time} microseconds to render page '#{file_path}'.")
100
+    Logger.debug("Completed render of page.",
101
+      theme_name: theme_name,
102
+      file_path: file_path,
103
+      time_ms: time
104
+    )
95 105
 
96 106
     value
97 107
   end

+ 17
- 6
lib/theme.ex View File

@@ -3,12 +3,17 @@ defmodule Koype.Theme do
3 3
 
4 4
   def root_directory(), do: @root_directory
5 5
 
6
-  def config_path_for(theme, kind \\ :info)
6
+  def config_path_for(theme \\ "default", kind \\ :info)
7 7
 
8
-  def config_path_for(theme, kind) when kind in ~w(info meta)a,
8
+  def config_path_for(theme, kind) when kind in ~w(info meta)a and not is_nil(theme),
9 9
     do: Path.join([File.cwd!(), @root_directory, theme, ".koype/#{kind}.json"])
10 10
 
11
-  def template_dir(theme), do: Path.join([File.cwd!(), @root_directory, theme, "tmpl"])
11
+  def config_path_for(_theme, _kind), do: nil
12
+
13
+  def template_dir(theme) when is_binary(theme),
14
+    do: Path.join([File.cwd!(), @root_directory, theme, "tmpl"])
15
+
16
+  def template_dir(_), do: nil
12 17
 
13 18
   def all() do
14 19
     case File.ls(@root_directory) do
@@ -34,7 +39,8 @@ defmodule Koype.Theme do
34 39
 
35 40
   def config_for(theme, kind) do
36 41
     with(
37
-      {:ok, json_str} <- File.read(config_path_for(theme, kind)),
42
+      config_path when is_binary(config_path) <- config_path_for(theme, kind),
43
+      {:ok, json_str} <- File.read(config_path),
38 44
       {:ok, json_obj} <- Jason.decode(json_str)
39 45
     ) do
40 46
       {:ok, json_obj |> Map.put("id", theme)}
@@ -44,8 +50,13 @@ defmodule Koype.Theme do
44 50
     end
45 51
   end
46 52
 
47
-  def current(), do: Koype.Setting.get("active_theme")
48
-  def set(name), do: Koype.Setting.set("active_theme", name)
53
+  @doc "Provides the name of the theme Koype will use by default."
54
+  @spec current() :: binary()
55
+  def current(), do: Koype.Setting.get("active_theme", "default")
56
+
57
+  @spec set(binary()) :: :ok | {:error, any()}
58
+  def set(name \\ "default")
59
+  def set(name) when is_binary(name), do: Koype.Setting.set("active_theme", name)
49 60
 
50 61
   def paginate(params \\ [page: 1, page_size: 5]) do
51 62
     Scrivener.paginate(all(), params)

+ 9
- 7
lib/web.ex View File

@@ -1,19 +1,19 @@
1 1
 # Koype: a IndieWeb-focused, single-tenant website engine for people.
2
-# 
2
+#
3 3
 # Copyright © 2019 Jacky Alciné <jacky.is@black.af>
4
-# 
4
+#
5 5
 # This file belongs to the Koype project.
6
-# 
6
+#
7 7
 # This program is free software: you can redistribute it and/or modify
8 8
 # it under the terms of the GNU Affero General Public License as published by
9 9
 # the Free Software Foundation, either version 3 of the License, or
10 10
 # (at your option) any later version.
11
-# 
11
+#
12 12
 # This program is distributed in the hope that it will be useful,
13 13
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 14
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 15
 # GNU Affero General Public License for more details.
16
-# 
16
+#
17 17
 # You should have received a copy of the GNU Affero General Public License
18 18
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
19 19
 defmodule Koype.Web do
@@ -26,12 +26,14 @@ defmodule Koype.Web do
26 26
       import Koype.Web.Gettext
27 27
       import Koype.Web.AuthenticationHelpers
28 28
       import Koype.Web.Router.Helpers
29
+      import Koype.Web.Plug.Theme, only: [current: 1]
29 30
       require Logger
30 31
 
31 32
       action_fallback(Koype.Web.FallbackController)
32 33
 
33
-      def generate_template_page(conn, page_name, assigns \\ %{}) do
34
-        current_template = conn.private[:koype_theme] || "default"
34
+      def(generate_template_page(conn, page_name, assigns \\ %{})) do
35
+        current_template = current(conn)
36
+        Logger.info("Using provided theme for this page.", theme_name: inspect(current_template))
35 37
         {:safe, class_names} = Koype.Web.LayoutView.css_class_names(conn)
36 38
 
37 39
         params =

+ 26
- 0
test/integration/plug/theme_test.exs View File

@@ -0,0 +1,26 @@
1
+defmodule Koype.Web.Plug.ThemeTest do
2
+  use Koype.Web.ConnCase
3
+  use Koype.DataCase
4
+  alias Koype.Web.Plug.Theme, as: Subject
5
+
6
+  describe ".call/2" do
7
+    test "makes use of provided theme from params" do
8
+      conn = build_conn() |> get("/?_koype_theme=foobar") |> Subject.call([])
9
+      theme_name = conn.assigns[:koype_theme]
10
+      assert "foobar" == theme_name
11
+    end
12
+
13
+    test "makes use of provided theme from header" do
14
+      conn = build_conn() |> put_req_header("x-koype-theme", "foobar") |> Subject.call([])
15
+      theme_name = conn.assigns[:koype_theme]
16
+      assert "foobar" == theme_name
17
+    end
18
+
19
+    test "defaults to system defined theme" do
20
+      Koype.Theme.set("default")
21
+      conn = build_conn() |> Subject.call([])
22
+      theme_name = conn.assigns[:koype_theme]
23
+      assert "default" == theme_name
24
+    end
25
+  end
26
+end

+ 8
- 30
test/integration/template_test.exs View File

@@ -9,15 +9,12 @@ defmodule Koype.Integration.TemplateTest do
9 9
     :ok
10 10
   end
11 11
 
12
-  # TODO: Test 'navigator'
13
-  # TODO: Test 'simplest'
14
-  # TODO: Test 'brutual'
15
-  @inbuilt_themes ~w(simplest navigator so-simple)s
12
+  @inbuilt_themes ~w(simplest)s
16 13
   @test_mf2_url "https://v2.jacky.wtf/post/fc7df928-fbcd-43ea-8c7f-5f99fef674be"
17 14
 
18 15
   for template_name <- ~w(default)s ++ @inbuilt_themes do
19 16
     describe template_name <> " - renders as expected" do
20
-      test "200 home" do
17
+      test "200 ['home'] renders the homepage" do
21 18
         resp = build_conn() |> template(unquote(template_name)) |> get("/")
22 19
 
23 20
         assert html_response(
@@ -28,7 +25,7 @@ defmodule Koype.Integration.TemplateTest do
28 25
         assert {"x-koype-theme", unquote(template_name)} in resp.resp_headers
29 26
       end
30 27
 
31
-      test "200 follow" do
28
+      test "200 ['follow'] renders the following page" do
32 29
         resp = build_conn() |> template(unquote(template_name)) |> get("/follow")
33 30
 
34 31
         assert html_response(
@@ -39,7 +36,7 @@ defmodule Koype.Integration.TemplateTest do
39 36
         assert {"x-koype-theme", unquote(template_name)} in resp.resp_headers
40 37
       end
41 38
 
42
-      test "200 stream/page" do
39
+      test "200 ['stream/page'] renders a pagination page" do
43 40
         insert_list(6, :entry,
44 41
           visibility: "public",
45 42
           post_status: "published",
@@ -71,24 +68,6 @@ defmodule Koype.Integration.TemplateTest do
71 68
         assert length(entries) == 6
72 69
       end
73 70
 
74
-      test "200 stream/page - some posts are media-centric" do
75
-        insert_list(5, :entry)
76
-        |> Enum.each(
77
-          &Koype.Repo.Entry.json_persist(&1, build(:entry_json) |> with_post_type(:single_photo))
78
-        )
79
-
80
-        resp = build_conn() |> template(unquote(template_name)) |> get("/stream")
81
-
82
-        assert html =
83
-                 html_response(
84
-                   resp,
85
-                   :ok
86
-                 )
87
-
88
-        assert {"x-koype-theme", unquote(template_name)} in resp.resp_headers
89
-        assert html =~ "u-photo"
90
-      end
91
-
92 71
       test "200 stream/empty" do
93 72
         resp = build_conn() |> template(unquote(template_name)) |> get("/stream")
94 73
 
@@ -408,7 +387,7 @@ defmodule Koype.Integration.TemplateTest do
408 387
         entry_mf2 = Enum.find(mf2[:items], fn entry -> "h-entry" in entry[:type] end)
409 388
 
410 389
         refute is_nil(entry_mf2)
411
-        assert %{html: "", text: ""} in entry_mf2[:properties][:content]
390
+        refute entry_mf2[:properties][:content]
412 391
       end
413 392
 
414 393
       test "200 entry/view - text-only content in entry" do
@@ -439,10 +418,9 @@ defmodule Koype.Integration.TemplateTest do
439 418
       end
440 419
 
441 420
       test "410 entry/gone" do
442
-        entry = insert(:entry)
421
+        {:ok, entry} = insert(:entry) |> Koype.Repo.Entry.delete()
443 422
         Koype.Repo.Entry.json_persist(entry, build(:entry_json))
444 423
         entry_url = Koype.Repo.Entry.get_uri(entry)
445
-        Koype.Repo.Entry.delete(entry)
446 424
         resp = build_conn() |> template(unquote(template_name)) |> get(entry_url)
447 425
 
448 426
         assert html =
@@ -456,8 +434,8 @@ defmodule Koype.Integration.TemplateTest do
456 434
         refute html =~ entry.name
457 435
       end
458 436
 
459
-      test "200 entry/not-found" do
460
-        entry = insert(:entry)
437
+      test "entry/not-found" do
438
+        entry = insert(:entry, name: Faker.Lorem.sentence())
461 439
         entry_url = Koype.Repo.Entry.get_uri(entry)
462 440
         resp = build_conn() |> template(unquote(template_name)) |> get(entry_url)
463 441
 

+ 1
- 1
test/support/steps/data.ex View File

@@ -41,7 +41,7 @@ defmodule Koype.Feature.Steps.Shared.Data do
41 41
             "type" => type |> String.trim_leading("single_") |> String.trim_leading("multiple_")
42 42
           })
43 43
 
44
-        {:ok, _} = Koype.Repo.Entry.json_persist(record, Apex.ap(entry_json))
44
+        {:ok, _} = Koype.Repo.Entry.json_persist(record, entry_json)
45 45
 
46 46
         {:ok, %{record: new_record}}
47 47
       end

+ 1
- 1
test/support/template_case.ex View File

@@ -19,6 +19,6 @@ defmodule Koype.Web.TemplateCase do
19 19
   end
20 20
 
21 21
   def template(%Plug.Conn{} = conn, template_name \\ "default") do
22
-    conn |> put_req_header("x-koype-theme", template_name)
22
+    conn |> assign(:koype_theme, template_name) |> Koype.Web.Plug.Theme.call([])
23 23
   end
24 24
 end

+ 4
- 3
web/endpoint.ex View File

@@ -56,6 +56,10 @@ defmodule Koype.Web.Endpoint do
56 56
     extra: "SameSite=Strict"
57 57
   )
58 58
 
59
+  plug(Koype.Web.Plug.System)
60
+  plug(Koype.Web.Plug.Client.CORS)
61
+  plug(Koype.Web.Plug.Theme)
62
+
59 63
   if Mix.env() == :dev do
60 64
     use(Plug.Debugger, otp_app: :koype, primary: "#232323", logo: nil, accent: "#85144b")
61 65
 
@@ -66,9 +70,6 @@ defmodule Koype.Web.Endpoint do
66 70
     end
67 71
   end
68 72
 
69
-  plug(Koype.Web.Plug.System)
70
-  plug(Koype.Web.Plug.Client.CORS)
71
-  plug(Koype.Web.Plug.Theme)
72 73
   plug(Koype.Web.Router)
73 74
 
74 75
   def init(_key, config) do

+ 1
- 1
web/plug/system.ex View File

@@ -32,7 +32,7 @@ defmodule Koype.Web.Plug.System do
32 32
       },
33 33
       {
34 34
         "x-koype-theme",
35
-        Map.get(conn.private, :koype_theme, :default) |> Atom.to_string()
35
+        Map.get(conn.assigns, :koype_theme, "default")
36 36
       }
37 37
     ])
38 38
   end

+ 27
- 12
web/plug/theme.ex View File

@@ -1,21 +1,36 @@
1 1
 defmodule Koype.Web.Plug.Theme do
2
-  import Plug.Conn, only: [put_private: 3]
3
-  @spec init(keyword()) :: keyword()
2
+  import Plug.Conn
3
+  require Logger
4
+
4 5
   def init(opts), do: opts
5 6
 
6
-  @spec call(Plug.Conn.t(), keyword()) :: Plug.Conn.t()
7
-  def call(conn, _) do
8
-    theme_name = do_fetch_theme_name(conn) || Koype.Theme.current()
7
+  def call(conn, opts)
9 8
 
9
+  def call(%Plug.Conn{assigns: %{koype_theme: theme}} = conn, _) when is_binary(theme) do
10
+    Logger.debug("Using pre-configured theme.", theme_name: theme)
10 11
     conn
11
-    |> put_private(:koype_theme, theme_name)
12 12
   end
13 13
 
14
-  @spec call(Plug.Conn.t(), keyword()) :: String.t() | nil
15
-  defp do_fetch_theme_name(%{req_headers: %{"x-koype-theme" => theme}})
16
-       when is_binary(theme),
17
-       do: theme
14
+  def call(conn, _) do
15
+    header_value = Plug.Conn.get_req_header(conn, "x-koype-theme") |> List.wrap() |> List.first()
16
+
17
+    params_value =
18
+      conn |> Plug.Conn.fetch_query_params() |> Map.get(:params) |> Map.get("_koype_theme")
19
+
20
+    theme_name =
21
+      cond do
22
+        is_binary(header_value) -> header_value
23
+        is_binary(params_value) -> params_value
24
+        true -> Koype.Theme.current()
25
+      end
18 26
 
19
-  defp do_fetch_theme_name(%{params: %{"_theme" => theme}}) when is_binary(theme), do: theme
20
-  defp do_fetch_theme_name(_), do: nil
27
+    Logger.debug("Making use of requested theme.", theme_name: theme_name)
28
+
29
+    conn
30
+    |> assign(:koype_theme, theme_name)
31
+  end
32
+
33
+  def current(conn) do
34
+    Map.get(conn.assigns, :koype_theme)
35
+  end
21 36
 end

+ 8
- 0
web/static/koype.scss View File

@@ -304,3 +304,11 @@ div[data-post-type]
304 304
   --background-color: #E8FDF5;
305 305
   --color: #001B44;
306 306
 }
307
+
308
+article.h-entry[data-post-type=article] .e-content p:first-child:first-line {
309
+  font-size: 2.5rem;
310
+  text-transform: uppercase;
311
+  display: inline-block;
312
+  font-weight: bolder;
313
+  line-height: 1;
314
+}

+ 2
- 3
web/views/layout_view.ex View File

@@ -72,7 +72,7 @@ defmodule Koype.Web.LayoutView do
72 72
   def tags(type, template, assigns)
73 73
 
74 74
   def tags(:meta, _, _),
75
-    do: ~w(description application_name creator author publisher x-koype-theme-id)a
75
+    do: ~w(description application_name creator author publisher)a
76 76
 
77 77
   def tags(:link, _, _),
78 78
     do:
@@ -86,8 +86,7 @@ defmodule Koype.Web.LayoutView do
86 86
       description: "IndieWeb under your terms.",
87 87
       author: Koype.Profile.displayed_name(),
88 88
       creator: "Koype",
89
-      publisher: Koype.Profile.displayed_name(),
90
-      "x-koype-theme-id": Koype.Theme.current()
89
+      publisher: Koype.Profile.displayed_name()
91 90
     }
92 91
 
93 92
     %{name: name, content: urls[name]}

Loading…
Cancel
Save