Current section

37 Versions

Jump to

Compare versions

28 files changed
+918 additions
-289 deletions
  @@ -1,13 +1,26 @@
1 - # Thesis
1 + # Thesis Content Editing System
2 2
3 - Thesis is an Elixir/Phoenix hex package for quickly and easily adding content
4 - editing to any page.
3 + [![Build Status](https://semaphoreci.com/api/v1/projects/0540dbb0-887a-45dd-9190-baa19b2ca9fb/816876/badge.svg)](https://semaphoreci.com/ir/thesis-phoenix)
5 4
6 - It's inspired by the [Rails gem](https://github.com/infinitered/thesis-rails) by
7 - the same name and author.
5 + Thesis is a lightweight and flexible Elixir/Phoenix hex package for quickly and easily
6 + adding content editing to any page on a Phoenix website.
7 +
8 + It's not quite a Phoenix CMS, so we call it a Phoenix CES -- content editing system.
9 +
10 + See also the Thesis [Rails gem](https://github.com/infinitered/thesis-rails).
8 11
9 12 ![screen capture on 2016-04-20 at 15-11-10 copy](https://cloud.githubusercontent.com/assets/1775841/14692261/d4734d3a-070a-11e6-866b-eebbc40e6157.gif)
10 13
14 + ## Thesis Features
15 +
16 + * Elixir/Phoenix hex package, uses React.js for its user interface
17 + * Lightweight, bolt-on, doesn't hijack your development workflow
18 + * On-page rich text editing
19 + * On-page plain text editing
20 + * Raw HTML editing for Youtube embeds or other flexible uses
21 + * Image URL editing, both `img` tag and `div` with background image
22 + * Page meta title and description editing
23 + * Easily bring your own authentication system in one tiny function
11 24
12 25 ## Installation and Configuration
13 26
  @@ -17,7 +30,7 @@ _If you are having problems, view `README_INSTALL.md` for manual instructions._
17 30
18 31 ```elixir
19 32 def deps do
20 - [{:thesis, "~> 0.0.16"}]
33 + [{:thesis, "~> 0.0.17"}]
21 34 end
22 35
23 36 def application do
  @@ -28,7 +41,7 @@ end
28 41 #### 2. Run `mix thesis.install`
29 42
30 43 This install script will add Thesis to your `config.exs` and `web.ex`, as well
31 - as generate a migration and an authorization module in `lib/thesis_auth.ex`.
44 + as generate migrations and an authorization module in your `lib/thesis_auth.ex`.
32 45
33 46 #### 3. Add the Thesis editor to your layout
34 47
  @@ -46,15 +59,15 @@ $ mix ecto.migrate
46 59 ## Making Pages Editable
47 60
48 61 Use the `Thesis.View.content/4` view helper function to make a content area
49 - editable. If you added `use Thesis.View` in your `web.ex` file, this function
62 + editable. If you have `use Thesis.View` in your `web.ex` file, this function
50 63 is already available on all of your views.
51 64
52 65 Thesis will add a wrapper `<div>` around editable HTML and plain-text content
53 - areas.
66 + areas, both in read mode and edit mode, so plan your CSS accordingly.
54 67
55 68 ### Rich Text Areas
56 69
57 - Simply wrap your HTML in a `content` function call.
70 + Simply wrap your HTML in a `content` function call, specifying `html` as the content type.
58 71
59 72 ```eex
60 73 <h1>Title</h1>
  @@ -88,12 +101,59 @@ becomes...
88 101 <h1><%= content(@conn, "Title identifier", :text, do: "My Title") %></h1>
89 102 ```
90 103
104 + ### Custom HTML Areas
105 +
106 + For video embeds, iframes, and any other custom HTML, use the `:raw_html` content type:
107 +
108 + ```eex
109 + <iframe width="560" height="315" src="https://www.youtube.com/embed/5SVLs_NN_uY" frameborder="0" allowfullscreen></iframe>
110 + ```
111 +
112 + becomes...
113 +
114 + ```eex
115 + <%= content(@conn, "Section identifier", :raw_html) do %>
116 + <iframe width="560" height="315" src="https://www.youtube.com/embed/5SVLs_NN_uY" frameborder="0" allowfullscreen></iframe>
117 + <% end %>
118 + ```
119 +
120 + ### Images (by URL)
121 +
122 + You can have the user specify an image URL and display the image with the `image` content type.
123 +
124 + ```eex
125 + <img src="http://placekitten.com/200/300">
126 + ```
127 +
128 + becomes...
129 +
130 + ```eex
131 + <%= content(@conn, "Image identifier", :image, alt: "My alt tag", do: "http://placekitten.com/200/300")
132 + %>
133 + ```
134 +
135 + If you prefer to use a `div` with a background image, you can use the `background_image`
136 + content type.
137 +
138 + ```eex
139 + <div style="background-image: url(http://placekitten.com/200/300)"></div>
140 + ```
141 +
142 + becomes...
143 +
144 + ```eex
145 + <%= content(@conn, "Image identifier", :background_image, do: "http://placekitten.com/200/300")
146 + %>
147 + ```
148 +
149 + _Note: Image uploads are coming soon._
150 +
91 151 ### Global Content Areas
92 152
93 153 Content areas in Thesis are page-specific. However, if you want an editable
94 - region that can be displayed on multiple pages, use the
95 - `Thesis.View.global_content/4` function. Internally, the page_id will be set
96 - to `nil` to indicate it applies globally, rather than to a specific page.
154 + area that can be displayed on multiple pages, use the
155 + `Thesis.View.global_content/4` function. Any page using that content area identifier
156 + will display the edited content across the whole website.
97 157
98 158 ```eex
99 159 <%= global_content(@conn, "Footer Text", :html) do %>
  @@ -105,23 +165,36 @@ to `nil` to indicate it applies globally, rather than to a specific page.
105 165 <% end %>
106 166 ```
107 167
108 - ### Meta Title and Description
168 + ### Customizing the Thesis markup
109 169
110 - In your layout, you can output the current title and description like so:
170 + Thesis adds an additional `<div>` around your editable content areas. We suggest that
171 + you not style these divs heavily, since Thesis uses them as editors and adds its own styles
172 + in edit-mode. However, sometimes, you need to modify that markup slightly for better presentation.
173 + You can provide an ID and additional classes by specifying `id` and `classes`, respectively.
174 +
175 + ```eex
176 + <%= content(@conn, "Ident", :html, id: "my-id", classes: "more classes") do %>
177 + <h1>Title</h1>
178 + <% end %>
179 + ```
180 +
181 + ### Page Meta Title and Description
182 +
183 + Thesis provides a settings tray to edit each page's title and description. In your
184 + layout, you can output the current title and description like so:
111 185
112 186 ```eex
113 187 <title><%= page_title(@conn, "Default Title") %></title>
114 188 <meta name="description" content="<%= page_description(@conn, "Default Description") %>" />
115 189 ```
116 190
117 - Some prefer to set the page title and description in their controller actions:
191 + Some prefer to set the page title and description as assigns in their controller actions:
118 192
119 193 ```eex
120 194 def about(conn, params) do
121 195 @title = Thesis.View.page_title(conn, "About My Company")
122 196 @description = Thesis.View.page_description(conn, "A relevant description here.")
123 197 end
124 -
125 198 ```
126 199
127 200 ## Authorization
  @@ -138,6 +211,8 @@ Here's an example which we use on our own website, [https://infinite.red](https:
138 211
139 212 ```elixir
140 213 defmodule IrWebsite.ThesisAuth do
214 + @behaviour Thesis.Auth
215 +
141 216 def page_is_editable?(conn) do
142 217 IrWebsite.AuthController.logged_in?(conn)
143 218 end
  @@ -161,15 +236,32 @@ So, in this case, we're simply checking to see if the user has been logged in
161 236 or not. Since only Infinite Red employees have logins, it's safe for us to
162 237 assume that if they're logged in, they have permission to edit the page.
163 238
239 + If you use [Guardian](https://github.com/ueberauth/guardian) or something similar,
240 + you may need additional manipulations to your `conn` to properly authenticate the
241 + user. Add those to your auth module like this:
242 +
243 + ```eex
244 + defmodule MyApp.ThesisAuth do
245 + @moduledoc """
246 + Contains functions for handling Thesis authorization.
247 + """
248 +
249 + def page_is_editable?(conn) do
250 + conn
251 + |> Guardian.Plug.VerifySession.call(%{})
252 + |> Guardian.Plug.LoadResource.call(%{})
253 + |> MyApp.SessionController.logged_in_and_admin?
254 + end
255 + end
256 + ```
257 +
164 258 ## What Thesis Isn't
165 259
166 260 You can't have it all. Thesis isn't the same as other -bloated- full-function
167 261 content management systems out there. This is a list of what it's not now and
168 262 not likely to be in the future.
169 263
170 - _We reserve the right to change our mind, however. :-)_
171 -
172 - * Not a WordPress Replacement
264 + * Not a complete WordPress Replacement
173 265 * Not a full featured CMS
174 266 * Not a full featured WYSIWYG editor
175 267 * Not an authentication or permission system
  @@ -185,7 +277,7 @@ websites. Please help us improve!
185 277 3. Commit your changes (`git commit -am 'Add some feature'`)
186 278 4. Run `brunch watch` during development
187 279 5. Write tests for your new feature
188 - 6. Run `rake spec` in the root directory to ensure that all tests pass.
280 + 6. Run `mix test` in the root directory to ensure that all tests pass.
189 281 7. Push to the branch (`git push origin my-new-feature`)
190 282 8. Create new Pull Request
191 283
  @@ -194,6 +286,7 @@ websites. Please help us improve!
194 286 * Jamon Holmgren [@jamonholmgren](https://twitter.com/jamonholmgren)
195 287 * Ken Miller [@seriousken](https://github.com/kemiller)
196 288 * Daniel Berkompas [@dberkom](https://twitter.com/dberkom)
289 + * Yulian Glukhenko [@yulianglukhenko](https://github.com/yulianglukhenko)
197 290
198 291 Also supported by others on the [Infinite Red](https://infinite.red) team.
  @@ -6,7 +6,7 @@ For automatic setup, see `README.md`.
6 6
7 7 ```elixir
8 8 def deps do
9 - [{:thesis, "~> 0.0.16"}]
9 + [{:thesis, "~> 0.0.17"}]
10 10 end
11 11
12 12 def application do
  @@ -21,7 +21,7 @@ Run `mix deps.get`.
21 21 ```elixir
22 22 config :thesis,
23 23 store: Thesis.EctoStore,
24 - authorization: IrWebsite.ThesisAuth
24 + authorization: <MyApp>.ThesisAuth
25 25 config :thesis, Thesis.EctoStore, repo: <MyApp>.Repo
26 26 ```
27 27
  @@ -29,6 +29,8 @@ config :thesis, Thesis.EctoStore, repo: <MyApp>.Repo
29 29
30 30 ```elixir
31 31 defmodule <MyApp>.ThesisAuth do
32 + @behaviour Thesis.Auth
33 +
32 34 def page_is_editable?(conn) do
33 35 true # editable by the world
34 36 end
  @@ -55,6 +57,7 @@ defmodule <MyApp>.Repo.Migrations.CreateThesisTables do
55 57 add :name, :string, nil: false
56 58 add :content, :text, default: "Edit this content area"
57 59 add :content_type, :string, default: "html"
60 + add :meta, :text
58 61
59 62 timestamps
60 63 end
  @@ -75,6 +78,35 @@ end
75 78 $ mix ecto.migrate
76 79 ```
77 80
78 - #### 7. Done!
81 + #### 7. Add Thesis.Controller, Thesis.View, and Thesis.Router to your `web/web.ex` file
82 +
83 + ```elixir
84 + def controller do
85 + quote do
86 + use Phoenix.Controller
87 + use Thesis.Controller
88 +
89 + # ...
90 + end
91 + end
92 +
93 + def view do
94 + quote do
95 + use Phoenix.View, root: "web/templates"
96 + use Thesis.View
97 +
98 + # ...
99 + end
100 + end
101 +
102 + def router do
103 + quote do
104 + use Phoenix.Router
105 + use Thesis.Router
106 + end
107 + end
108 + ```
109 +
110 + #### 8. Done!
79 111
80 112 Now, start adding content areas. See the `README.md` for further instructions.
  @@ -6,12 +6,14 @@
6 6 {<<"files">>,
7 7 [<<"lib/mix/tasks/thesis.install.ex">>,<<"lib/mix/utils.ex">>,
8 8 <<"lib/thesis.ex">>,<<"lib/thesis/api_controller.ex">>,
9 - <<"lib/thesis/config.ex">>,<<"lib/thesis/controller.ex">>,
10 - <<"lib/thesis/models/page.ex">>,<<"lib/thesis/models/page_content.ex">>,
9 + <<"lib/thesis/auth.ex">>,<<"lib/thesis/config.ex">>,
10 + <<"lib/thesis/controller.ex">>,<<"lib/thesis/models/page.ex">>,
11 + <<"lib/thesis/models/page_content.ex">>,<<"lib/thesis/render.ex">>,
11 12 <<"lib/thesis/router.ex">>,<<"lib/thesis/store.ex">>,
12 13 <<"lib/thesis/stores/ecto_store.ex">>,<<"lib/thesis/view.ex">>,
13 14 <<"priv/static/thesis-editor.js">>,<<"priv/static/thesis.css">>,
14 - <<"priv/templates/thesis.install/migration.exs">>,
15 + <<"priv/templates/thesis.install/add_meta_to_thesis_page_contents.exs">>,
16 + <<"priv/templates/thesis.install/create_thesis_tables.exs">>,
15 17 <<"priv/templates/thesis.install/thesis_auth.exs">>,
16 18 <<"web/static/css/_extends/_extends.sass">>,
17 19 <<"web/static/css/_extends/_forms.sass">>,
  @@ -112,15 +114,19 @@
112 114 <<"web/static/js/components/cancel_button.js">>,
113 115 <<"web/static/js/components/delete_button.js">>,
114 116 <<"web/static/js/components/edit_button.js">>,
117 + <<"web/static/js/components/image_tray.js">>,
118 + <<"web/static/js/components/raw_html_tray.js">>,
115 119 <<"web/static/js/components/save_button.js">>,
116 120 <<"web/static/js/components/settings_button.js">>,
117 121 <<"web/static/js/components/settings_tray.js">>,
122 + <<"web/static/js/content_types/html.js">>,
123 + <<"web/static/js/content_types/raw_html.js">>,
118 124 <<"web/static/js/thesis-editor.js">>,<<"web/static/js/utilities/net.js">>,
119 125 <<"mix.exs">>,<<"README.md">>,<<"README_INSTALL.md">>,<<"LICENSE.md">>,
120 126 <<"package.json">>]}.
121 127 {<<"licenses">>,[<<"MIT">>]}.
122 128 {<<"links">>,
123 - [{<<"Docs">>,<<"https://hexdocs.pm/thesis/0.0.16/api-reference.html">>},
129 + [{<<"Docs">>,<<"https://hexdocs.pm/thesis/0.0.17/api-reference.html">>},
124 130 {<<"GitHub">>,<<"https://github.com/infinite_red/thesis">>}]}.
125 131 {<<"maintainers">>,
126 132 [<<"Jamon Holmgren">>,<<"Ken Miller">>,<<"Daniel Berkompas">>]}.
  @@ -129,21 +135,25 @@
129 135 [[{<<"app">>,<<"phoenix">>},
130 136 {<<"name">>,<<"phoenix">>},
131 137 {<<"optional">>,false},
132 - {<<"requirement">>,<<">= 0.0.0">>}],
138 + {<<"requirement">>,<<">= 1.0.0">>}],
133 139 [{<<"app">>,<<"phoenix_html">>},
134 140 {<<"name">>,<<"phoenix_html">>},
135 141 {<<"optional">>,false},
136 - {<<"requirement">>,<<">= 0.0.0">>}],
142 + {<<"requirement">>,<<">= 2.0.0">>}],
137 143 [{<<"app">>,<<"ecto">>},
138 144 {<<"name">>,<<"ecto">>},
139 145 {<<"optional">>,false},
140 - {<<"requirement">>,<<">= 0.0.0">>}],
146 + {<<"requirement">>,<<">= 2.0.0">>}],
141 147 [{<<"app">>,<<"plug">>},
142 148 {<<"name">>,<<"plug">>},
143 149 {<<"optional">>,false},
144 - {<<"requirement">>,<<"~> 1.0">>}],
150 + {<<"requirement">>,<<">= 1.0.0">>}],
151 + [{<<"app">>,<<"poison">>},
152 + {<<"name">>,<<"poison">>},
153 + {<<"optional">>,false},
154 + {<<"requirement">>,<<">= 1.0.0">>}],
145 155 [{<<"app">>,<<"html_sanitize_ex">>},
146 156 {<<"name">>,<<"html_sanitize_ex">>},
147 157 {<<"optional">>,false},
148 - {<<"requirement">>,<<"~> 1.0.0">>}]]}.
149 - {<<"version">>,<<"0.0.16">>}.
158 + {<<"requirement">>,<<">= 1.0.1">>}]]}.
159 + {<<"version">>,<<"0.0.17">>}.
  @@ -21,17 +21,21 @@ defmodule Mix.Tasks.Thesis.Install do
21 21 thesis_templates
22 22 thesis_config
23 23 thesis_web
24 +
25 + status_msg("done",
26 + "Now run #{IO.ANSI.blue}mix ecto.migrate#{IO.ANSI.reset} to ensure your database is up to date.")
24 27 end
25 28
26 29 @doc false
27 30 def thesis_templates do
28 - template_files = [ {"priv/templates/thesis.install/thesis_auth.exs", "lib/thesis_auth.ex" } ]
29 - migration_exists = File.ls!("priv/repo/migrations") |> Enum.map(fn (f) -> String.contains?(f, "create_thesis_tables") end) |> Enum.any?
30 - unless migration_exists do
31 - template_files = [{"priv/templates/thesis.install/migration.exs", "priv/repo/migrations/#{timestamp}_create_thesis_tables.exs"} | template_files]
32 - end
31 + migrations = ["create_thesis_tables", "add_meta_to_thesis_page_contents"]
32 + migration_files = Enum.filter_map(migrations, &migration_missing?/1, fn (filename) ->
33 + {"priv/templates/thesis.install/#{filename}.exs", "priv/repo/migrations/#{timestamp}_#{filename}.exs"}
34 + end)
33 35
34 - template_files
36 + template_files = [ {"priv/templates/thesis.install/thesis_auth.exs", "lib/thesis_auth.ex" } ]
37 +
38 + template_files ++ migration_files
35 39 |> Stream.map(&render_eex/1)
36 40 |> Stream.map(&copy_to_target/1)
37 41 |> Stream.run
  @@ -93,4 +97,9 @@ defmodule Mix.Tasks.Thesis.Install do
93 97 end
94 98 end
95 99
100 + defp migration_missing?(filename) do
101 + "priv/repo/migrations"
102 + |> File.ls!
103 + |> Enum.all?(fn (f) -> !String.contains?(f, filename) end)
104 + end
96 105 end
  @@ -1,14 +1,6 @@
1 1 defmodule Mix.Thesis.Utils do
2 2 @moduledoc false
3 3
4 - @doc false
5 - def get_package_path do
6 - __ENV__.file
7 - |> Path.dirname
8 - |> String.split("/lib/mix")
9 - |> hd
10 - end
11 -
12 4 @doc false
13 5 def get_module do
14 6 Mix.Project.get
  @@ -19,7 +11,7 @@ defmodule Mix.Thesis.Utils do
19 11
20 12 @doc false
21 13 def status_msg(status, message),
22 - do: IO.puts "#{IO.ANSI.green}* #{status}#{IO.ANSI.reset} #{message}"
14 + do: IO.puts "#{IO.ANSI.green}* #{String.rjust(status, 10)}#{IO.ANSI.reset} #{message}"
23 15
24 16 @doc false
25 17 def debug(message), do: IO.puts "==> #{message}"
  @@ -49,9 +41,10 @@ defmodule Mix.Thesis.Utils do
49 41 @doc false
50 42 def copy_to_target({contents, target}) do
51 43 if File.exists?(target) do
52 - status_msg("exists", "#{target}")
44 + status_msg("exists", target)
53 45 else
54 46 File.write!(target, contents)
47 + status_msg("creating", target)
55 48 end
56 49 end
Loading more files…